Java线程安全代码

时间:2018-05-15 11:42:10

标签: java multithreading thread-safety

我在网上看到了下面的代码,它说"代码不是线程安全的"。我不明白为什么?因为,以下每个线程运行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;
    }
}

3 个答案:

答案 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