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

Sey*_*emi 5 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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

And*_*niy 5

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

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

public IAmSoImmutable increment() {
            return new IAmSoImmutable(++value);
}
Run Code Online (Sandbox Code Playgroud)

是胡说八道有两个原因:

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

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

  • @austinpowers他给出了解决方案吗?我错过了. (3认同)
  • @austinpowers自动递增改变了`value`的值,因此合同被破坏了. (2认同)

rua*_*akh 4

这里的根本问题是您误解了“不变性”的含义。

“不变性”意味着——不能写入。值被创建,但永远不会被修改。

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

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

有了这个理解,我们就可以回到你最初的那句话:

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

这里的问题是,您实际上没有使用不可变对象:您的整个目标是执行写入,而不变性的整个概念是不发生写入。这些不兼容。

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

这里最好的方法可能是使用AtomicReference<IAmSoImmutable>. 这提供了一种非阻塞的方式来交换IAmSoImmutable-s,同时保证没有写入被默默地丢弃。

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