我在考虑如何解决两个线程之间的竞争条件,这两个线程尝试使用不可变对象写入同一个变量,而不帮助任何关键字,例如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);
}
}
如果您运行此代码,每次都会得到不同的答案,这意味着正在发生竞争条件。
答案 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)
即使问题通过以下方式解决:
IAmSoImmutable.value
increment()
内创建的新对象重新分配回iAmSoImmutable
参考。您的代码仍然不是原子的,需要一种同步。
解决方案当然是使用同步方法
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();
}
});