Java中线程安全问题的原因和解决方案

张开发
2026/4/6 1:33:21 15 分钟阅读

分享文章

Java中线程安全问题的原因和解决方案
共享资源多个线程都能访问到的资源如成员变量、静态变量、共享内存区域可变资源资源的状态值可以被修改如int计数器、HashMap的元素经典的i 操作。它在底层分为“读取-修改-写入”三个步骤。如果两个线程同时读取 i1各自加1后写回结果是2而不是3。直接原因三大特性被破坏Java内存模型JMM定义的多线程并发三大核心特性任何一个被破坏都会引发线程安全问题原子性一个操作如count包含“读 - 改 - 写”三步非原子操作会被多线程交错执行可见性线程修改共享变量后不会立即同步到主内存其他线程读取的仍是旧值有序性JVM的指令重排序优化会导致多线程下执行顺序混乱如未加volatile的双重检查锁单例。线程安全问题的解决方案核心思路要么避免共享可变资源从根源消除问题要么控制并发访问规则保证三大特性。方案1避免共享可变资源优先推荐栈封闭局部变量局部变量存储在线程私有栈中每个线程有独立副本天然线程安全。public class StackClosedDemo { // 每个线程调用该方法时都会创建独立的count副本 public void calculate() { int count 0; count; // 无线程安全问题 System.out.println(Thread.currentThread().getName() : count); } public static void main(String[] args) { StackClosedDemo demo new StackClosedDemo(); // 10个线程各自操作自己的局部变量 for (int i 0; i 10; i) { new Thread(demo::calculate, Thread- i).start(); } } }不可变对象:对象创建后状态不可修改如String、Integer即使共享也无法修改值。// 自定义不可变类final类final成员变量无setter public final class ImmutableUser { private final String name; private final int age; public ImmutableUser(String name, int age) { this.name name; this.age age; } // 仅提供getter无setter public String getName() { return name; } public int getAge() { return age; } }ThreadLocal线程本地存储:为每个线程提供独立的变量副本线程操作自身副本互不干扰。public class ThreadLocalDemo { // 每个线程有独立的Integer副本初始值为0 private static ThreadLocalInteger threadLocal ThreadLocal.withInitial(() - 0); public void increment() { threadLocal.set(threadLocal.get() 1); System.out.println(Thread.currentThread().getName() : threadLocal.get()); } public static void main(String[] args) { ThreadLocalDemo demo new ThreadLocalDemo(); // 3个线程各自操作自己的副本 for (int i 0; i 3; i) { new Thread(() - { for (int j 0; j 2; j) { demo.increment(); } }, Thread- i).start(); } } } // 输出顺序可能不同 // Thread-0: 1、Thread-0: 2 // Thread-1: 1、Thread-1: 2 // Thread-2: 1、Thread-2: 2方案2同步/加锁控制并发访问互斥同步阻塞同步:这是最常见的方案通过加锁来保证同一时刻只有一个线程操作资源。synchronized 关键字Java 原生支持使用简单。可修饰方法或代码块。属于不可中断的锁。public class SynchronizedDemo { private int count 0; // 同步实例方法锁是this对象 public synchronized void increment() { count; } public static void main(String[] args) throws InterruptedException { SynchronizedDemo demo new SynchronizedDemo(); // 1000个线程执行increment for (int i 0; i 1000; i) { new Thread(demo::increment).start(); } Thread.sleep(1000); System.out.println(最终count demo.count); // 输出1000 } }ReentrantLock显式锁:比synchronized灵活可中断、可超时、公平锁需手动释放锁必须在finally中。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockDemo { private int count 0; private Lock lock new ReentrantLock(); // 默认非公平锁 public void increment() { lock.lock(); // 加锁 try { count; } finally { lock.unlock(); // 释放锁避免死锁 } } public static void main(String[] args) throws InterruptedException { ReentrantLockDemo demo new ReentrantLockDemo(); for (int i 0; i 1000; i) { new Thread(demo::increment).start(); } Thread.sleep(1000); System.out.println(最终count demo.count); // 输出1000 } }方案3volatile关键字保证可见性/有序性保证可见性强制失效工作内存直接读写主内存。保证有序性禁止指令重排序。注意它不保证原子性不能解决i问题。public class VolatileDemo { private volatile boolean stop false; // 保证可见性和有序性 public void runThread() { new Thread(() - { int i 0; while (!stop) { // 能立即感知stop的修改 i; } System.out.println(线程停止i i); }).start(); } public static void main(String[] args) throws InterruptedException { VolatileDemo demo new VolatileDemo(); demo.runThread(); Thread.sleep(3000); demo.stop true; // 修改后线程立即停止 } }

更多文章