如何使用不可变对象解决两个作者的竞争条件

时间:2016-05-07 21:07:43

标签: java multithreading

我在考虑如何解决两个线程之间的竞争条件,这两个线程尝试使用不可变对象写入同一个变量,而不帮助任何关键字,例如java中的synchronize(lock)/ volatile。

但我无法弄明白,是否有可能用这样的解决方案解决这个问题?

public class Test {
    private static IAmSoImmutable iAmSoImmutable;

    private static final Runnable increment1000Times = () -> {
        for (int i = 0; i < 1000; i++) {
            iAmSoImmutable.increment();
        }
    };

    public static void main(String... args) throws Exception {
        for (int i = 0; i < 10; i++) {
            iAmSoImmutable = new IAmSoImmutable(0);

            Thread t1 = new Thread(increment1000Times);
            Thread t2 = new Thread(increment1000Times);

            t1.start();
            t2.start();

            t1.join();
            t2.join();

            // Prints a different result every time -- why? :
            System.out.println(iAmSoImmutable.value);
        }
    }

    public static class IAmSoImmutable {
        private int value;

        public IAmSoImmutable(int value) {
            this.value = value;
        }

        public IAmSoImmutable increment() {
            return new IAmSoImmutable(++value);
        }
    }

如果您运行此代码,每次都会得到不同的答案,这意味着正在发生竞争条件。

3 个答案:

答案 0 :(得分:5)

如果不使用任何存在同步(或易失性)技术,则无法解决竞争条件。那是他们的目的。如果有可能就不需要它们。

更具体地说,您的代码似乎已被破坏。这个方法:

public IAmSoImmutable increment() {
            return new IAmSoImmutable(++value);
}

是胡说八道有两个原因:

1)它破坏了类的不变性,因为它改变了对象的变量value

2)它的结果 - 类IAmSoImmutable的新实例 - 从未使用过。

答案 1 :(得分:4)

这里的根本问题是你误解了不可变性&#34;不变性&#34;装置

&#34;不变性&#34;意思是 - 没有写作。值已创建,但永远不会被修改。

不变性确保没有竞争条件,因为竞争条件总是由写入引起:两个线程执行彼此不一致的写入,或者一个线程执行写入而另一个线程执行读取会导致不一致结果,或类似的。

(警告:即使是一个不可变对象在构造过程中实际上是可变的 - Java创建对象,然后填充其字段 - 所以除了一般不可变之外,你需要适当地使用final关键字并注意你在构造函数中做了什么。但是,这些都是次要细节。)

根据这种理解,我们可以回到你的第一句话:

  

我在考虑如何解决两个线程之间的竞争条件,这两个线程尝试使用不可变对象写入同一个变量,而不帮助任何关键字,例如java中的synchronize(lock)/ volatile。

这里的问题是你实际上使用不可变对象:你的整个目标是执行写操作,整个不可变性的概念是不会发生写操作。这些不兼容。

那就是说,不变性肯定有它的位置。您可以拥有不可变的IAmSoImmutable对象,唯一的写入是您将这些对象互相交换。这有助于简化问题,通过减少您必须担心的写入范围:只有一种写入。但即使是那种写也需要同步。

这里最好的方法可能是使用AtomicReference<IAmSoImmutable>。这提供了一种非阻塞方式来交换IAmSoImmutable - s,同时保证不会以静默方式删除写入。

(事实上,在特殊情况下,您的值只是一个整数,JDK提供AtomicInteger来处理必要的比较和交换循环,以此类推线程安全增量。)

答案 2 :(得分:0)

即使问题通过以下方式解决:

  1. 避免更改IAmSoImmutable.value
  2. increment()内创建的新对象重新分配回iAmSoImmutable参考。
  3. 您的代码仍然不是原子的,需要一种同步。

    解决方案当然是使用同步方法

    public synchronized static void increment() {
        iAmSoImmutable = iAmSoImmutable.increment();
    }
    
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 1000; i++) {
            increment();
        }
    });
    
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 1000; i++) {
            increment();
        }
    });