找回密码
 立即注册
首页 业界区 业界 Java中线程安全问题的原因和解决方案

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

奸轲嫣 4 小时前
线程安全问题的核心原因


  • 线程安全问题本质是多个线程并发访问共享且可变的资源时,操作的原子性、可见性或有序性被破坏,导致程序执行结果不符合预期。

  • 根本原因:共享可变资源


  • 共享资源:多个线程都能访问到的资源(如成员变量、静态变量、共享内存区域);
  • 可变资源:资源的状态(值)可以被修改(如int计数器、HashMap的元素);
  • 经典的i++ 操作。它在底层分为“读取-修改-写入”三个步骤。如果两个线程同时读取 i=1,各自加1后写回,结果是2而不是3。

  • 直接原因:三大特性被破坏


  • Java内存模型(JMM)定义的多线程并发三大核心特性,任何一个被破坏都会引发线程安全问题:

    • 原子性:一个操作(如count++)包含“读 - 改 - 写”三步,非原子操作会被多线程交错执行;
    • 可见性:线程修改共享变量后,不会立即同步到主内存,其他线程读取的仍是旧值;
    • 有序性:JVM的指令重排序优化,会导致多线程下执行顺序混乱(如未加volatile的双重检查锁单例)。

线程安全问题的解决方案


  • 核心思路:要么避免共享可变资源(从根源消除问题),要么控制并发访问规则(保证三大特性)。
方案1:避免共享可变资源(优先推荐)


  • 栈封闭(局部变量):局部变量存储在线程私有栈中,每个线程有独立副本,天然线程安全。
  1. public class StackClosedDemo {
  2.     // 每个线程调用该方法时,都会创建独立的count副本
  3.     public void calculate() {
  4.         int count = 0;
  5.         count++; // 无线程安全问题
  6.         System.out.println(Thread.currentThread().getName() + ": " + count);
  7.     }
  8.     public static void main(String[] args) {
  9.         StackClosedDemo demo = new StackClosedDemo();
  10.         // 10个线程各自操作自己的局部变量
  11.         for (int i = 0; i < 10; i++) {
  12.             new Thread(demo::calculate, "Thread-" + i).start();
  13.         }
  14.     }
  15. }
复制代码

  • 不可变对象:对象创建后状态不可修改(如String、Integer),即使共享也无法修改值。
  1. // 自定义不可变类(final类+final成员变量+无setter)
  2. public final class ImmutableUser {
  3.     private final String name;
  4.     private final int age;
  5.     public ImmutableUser(String name, int age) {
  6.         this.name = name;
  7.         this.age = age;
  8.     }
  9.     // 仅提供getter,无setter
  10.     public String getName() { return name; }
  11.     public int getAge() { return age; }
  12. }
复制代码

  • ThreadLocal(线程本地存储):为每个线程提供独立的变量副本,线程操作自身副本,互不干扰。
  1. public class ThreadLocalDemo {
  2.     // 每个线程有独立的Integer副本,初始值为0
  3.     private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
  4.     public void increment() {
  5.         threadLocal.set(threadLocal.get() + 1);
  6.         System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
  7.     }
  8.     public static void main(String[] args) {
  9.         ThreadLocalDemo demo = new ThreadLocalDemo();
  10.         // 3个线程各自操作自己的副本
  11.         for (int i = 0; i < 3; i++) {
  12.             new Thread(() -> {
  13.                 for (int j = 0; j < 2; j++) {
  14.                     demo.increment();
  15.                 }
  16.             }, "Thread-" + i).start();
  17.         }
  18.     }
  19. }
  20. // 输出(顺序可能不同):
  21. // Thread-0: 1、Thread-0: 2
  22. // Thread-1: 1、Thread-1: 2
  23. // Thread-2: 1、Thread-2: 2
复制代码
方案2:同步/加锁(控制并发访问)

互斥同步(阻塞同步):这是最常见的方案,通过加锁来保证同一时刻只有一个线程操作资源。


  • synchronized 关键字:Java 原生支持,使用简单。可修饰方法或代码块。属于不可中断的锁。
  1. public class SynchronizedDemo {
  2.     private int count = 0;
  3.     // 同步实例方法,锁是this对象
  4.     public synchronized void increment() {
  5.         count++;
  6.     }
  7.     public static void main(String[] args) throws InterruptedException {
  8.         SynchronizedDemo demo = new SynchronizedDemo();
  9.         // 1000个线程执行increment
  10.         for (int i = 0; i < 1000; i++) {
  11.             new Thread(demo::increment).start();
  12.         }
  13.         Thread.sleep(1000);
  14.         System.out.println("最终count:" + demo.count); // 输出1000
  15.     }
  16. }
复制代码
ReentrantLock(显式锁):比synchronized灵活(可中断、可超时、公平锁),需手动释放锁(必须在finally中)。
  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. public class ReentrantLockDemo {
  4.     private int count = 0;
  5.     private Lock lock = new ReentrantLock(); // 默认非公平锁
  6.     public void increment() {
  7.         lock.lock(); // 加锁
  8.         try {
  9.             count++;
  10.         } finally {
  11.             lock.unlock(); // 释放锁,避免死锁
  12.         }
  13.     }
  14.     public static void main(String[] args) throws InterruptedException {
  15.         ReentrantLockDemo demo = new ReentrantLockDemo();
  16.         for (int i = 0; i < 1000; i++) {
  17.             new Thread(demo::increment).start();
  18.         }
  19.         Thread.sleep(1000);
  20.         System.out.println("最终count:" + demo.count); // 输出1000
  21.     }
  22. }
复制代码
方案3:volatile关键字(保证可见性/有序性)


  • 保证可见性:强制失效工作内存,直接读写主内存。
  • 保证有序性:禁止指令重排序。
  • 注意:它不保证原子性(不能解决i++问题)。
  1. public class VolatileDemo {
  2.     private volatile boolean stop = false; // 保证可见性和有序性
  3.     public void runThread() {
  4.         new Thread(() -> {
  5.             int i = 0;
  6.             while (!stop) { // 能立即感知stop的修改
  7.                 i++;
  8.             }
  9.             System.out.println("线程停止,i=" + i);
  10.         }).start();
  11.     }
  12.     public static void main(String[] args) throws InterruptedException {
  13.         VolatileDemo demo = new VolatileDemo();
  14.         demo.runThread();
  15.         Thread.sleep(3000);
  16.         demo.stop = true; // 修改后,线程立即停止
  17.     }
  18. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册