我在网上看到了下面的代码,它说"代码不是线程安全的"。我不明白为什么?因为,以下每个线程运行getList都不会让任何其他线程到达getList()
。
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();
}
private List<String> getList () {
if (list == null) {
list = new ArrayList<>();
}
return list;
}
}
答案 0 :(得分:6)
这一行使程序不是线程安全的
if (list == null) {
两个线程可能会同时看到列表为null并尝试为它们分配新数组。
答案 1 :(得分:3)
你有两个线程共享的可变状态(成员变量list
)。
两个线程可以访问:
if (list == null) {
并修改:
list = new ArrayList<>();
通过getlist
方法的共享可变状态,该方法未以任何方式同步。
因此,此代码不是线程安全的。通常,在没有同步的情况下访问和修改共享可变状态是个坏主意。
答案 2 :(得分:2)
这个代码不安全有两个原因,都是以这些陈述为中心:
if (list == null) {
list = new ArrayList<>();
}
return list;
第一个问题是存在竞争条件。在具有多个内核的系统上,两个线程几乎同时读取list
的概率很小,两个线程都会看到null
,并且将创建两个ArrayList
个对象,回。在单个核心系统上,读取list
后,一个线程将被另一个立即抢占的可能性更小,您将获得相同的结果。
但是有一个与Java内存模型相关的更隐蔽的问题。没有同步的事实意味着在一个线程写入list
和另一个线程(随后)读取它之间没有发生在之前的关系。这意味着Java解释器/ JIT编译器没有义务插入内存屏障序列,以确保较早线程写入的值对后一个线程可见。因此,即使在两个线程在不同时间运行的情况下......由于各种内存缓存效应,后一个线程可能看不到由前一个线程写入的非null值。
两个问题的解决方案是正确同步;例如像这样:
synchronized (this) {
if (list == null) {
list = new ArrayList<>();
}
return list;
}
或将方法声明为synchronized
。