You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

疑问:为何这段Java代码被标注为非线程安全?

为什么这段代码是非线程安全的?

嘿,我来帮你搞清楚这个问题~你之所以困惑,是因为误以为调用getList()的线程会自动互斥执行,但Java里普通方法默认没有任何线程同步机制,这就导致了代码里的竞态条件问题。

核心问题:竞态条件导致重复初始化

我们来模拟一下两个线程同时调用getList()的场景:

  • 线程1进入getList(),检查到list == null,准备执行list = new ArrayList<>(),但还没完成引用赋值(这个操作在JVM里不是原子的,分为创建对象、赋值引用两步);
  • 此时线程2也进入getList(),因为线程1还没把新对象的引用赋值给list,所以线程2看到的list仍然是null;
  • 线程2也会执行list = new ArrayList<>(),最终list会被两个线程先后赋值两次,导致两个线程可能拿到不同的ArrayList实例(看哪个线程先完成赋值)。

你可以运行这段代码多次,大概率会看到两个线程输出的identityHashCode不一样——这就是非线程安全的直接表现:本该只初始化一次的list被创建了多次,破坏了我们预期的“单例”行为。

你的误解:线程不会自动互斥

Java中,只有当方法被synchronized修饰,或者手动加锁时,才会保证同一时间只有一个线程进入方法/临界区。你的getList()是普通方法,多个线程可以同时进入执行,自然会出现上述的竞态问题。

修复方案

这里给你几种常见的修复方式,都能解决线程安全问题:

方案1:给getList()加synchronized修饰

最简单直接的方式,让整个方法成为同步方法,同一时间只有一个线程能进入:

public class MyClass { 
    private List<String> list; 
    public static void main (String[] args) throws InterruptedException { 
        MyClass obj = new MyClass(); 
        Thread thread1 = new Thread(() -> { 
            System.out.println("thread1 : " + System.identityHashCode(obj.getList())); 
        }); 
        Thread thread2 = new Thread(() -> { 
            System.out.println("thread2 : " + System.identityHashCode(obj.getList())); 
        }); 
        thread1.start(); 
        thread2.start(); 
    } 
    // 加synchronized保证线程互斥
    private synchronized List<String> getList () { 
        if (list == null) { 
            list = new ArrayList<>(); 
        } 
        return list; 
    } 
}

方案2:双重检查锁定(DCL)+ volatile

如果担心synchronized方法的性能开销(虽然这个场景下影响很小),可以用双重检查锁定,只在初始化阶段加锁:

public class MyClass { 
    // volatile修饰,禁止指令重排,保证多线程下的可见性
    private volatile List<String> list; 
    public static void main (String[] args) throws InterruptedException { 
        MyClass obj = new MyClass(); 
        Thread thread1 = new Thread(() -> { 
            System.out.println("thread1 : " + System.identityHashCode(obj.getList())); 
        }); 
        Thread thread2 = new Thread(() -> { 
            System.out.println("thread2 : " + System.identityHashCode(obj.getList())); 
        }); 
        thread1.start(); 
        thread2.start(); 
    } 
    private List<String> getList () { 
        if (list == null) { // 第一次无锁检查,避免每次都加锁
            synchronized (this) {
                if (list == null) { // 加锁后再次检查,防止竞态
                    list = new ArrayList<>(); 
                }
            }
        } 
        return list; 
    } 
}

注意:这里必须给list加volatile修饰,否则JVM的指令重排可能导致线程拿到未完全初始化的ArrayList实例。

方案3:静态内部类实现延迟加载

这是一种更优雅的线程安全单例方式,利用Java类加载机制天然保证线程安全:

public class MyClass { 
    // 静态内部类,只有当getList()调用时才会加载
    private static class ListHolder {
        private static final List<String> INSTANCE = new ArrayList<>();
    }
    
    public static void main (String[] args) throws InterruptedException { 
        MyClass obj = new MyClass(); 
        Thread thread1 = new Thread(() -> { 
            System.out.println("thread1 : " + System.identityHashCode(obj.getList())); 
        }); 
        Thread thread2 = new Thread(() -> { 
            System.out.println("thread2 : " + System.identityHashCode(obj.getList())); 
        }); 
        thread1.start(); 
        thread2.start(); 
    } 
    private List<String> getList () { 
        return ListHolder.INSTANCE; 
    } 
}

内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 10:01:42