为了试验多线程概念,我正在实现我自己的使用悲观锁定的AtomicInteger版本。它看起来像这样:
public class ThreadSafeInt {
public int i; // Should this be volatile?
public ThreadSafeInt(int i) {
this.i = i;
}
public synchronized int get() {
return i;
}
public synchronized int getAndIncrement() {
return this.i++;
}
// other synchronized methods for incrementAndGet(), etc...
}
我编写了一个测试,它接受一个ThreadSafeInt实例,将它提供给数百个线程,并使每个线程调用getAndIncrement 100,000次。我所看到的是所有增量都正确发生,整数的值正好是(number of threads) * (number of increments per thread)
,即使我没有在原始实例变量i
上使用volatile。我期望如果我没有使i
易变,那么我会得到很多可见性问题,例如,线程1将i
从0递增到1,但是线程2仍然看到0的值并且还将其增加到1
,导致最终值小于正确值。
我理解可见性问题是随机发生的,并且可能取决于我的环境的属性,因此即使可见性问题存在固有的可能性,我的测试也可能看起来很好。所以我倾向于认为volatile关键字仍然是必要的。
但这是对的吗?或者我的代码有一些属性(可能是它只是一个原始变量等),我实际上可以信任它来消除对volatile关键字的需求吗?
答案 0 :(得分:5)
即使我没有在原始实例变量i上使用volatile。我期望如果我没有让我变得不稳定,那么我会得到很多可见性问题
通过创建getAndIncrement()
和get()
方法synchronized
,正在修改i
的所有线程都正确锁定它以进行更新和检索值。 synchronized
块使i
不必volatile
,因为它们也可以确保内存同步。
也就是说,您应该使用AtomicInteger
代替volatile int
字段。 AtomicInteger
getAndIncrement()
方法更新了该值,而不必求助于synchronized
块,这个块更快,同时仍然是线程安全的。
public final AtomicInteger i = new AtomicInteger();
...
// no need for synchronized here
public int get() {
return i.get();
}
// nor here
public int getAndIncrement() {
return i.getAndIncrement();
}
我会遇到很多可见性问题,例如,线程1将i从0递增到1,但是线程2仍然看到值0并且还将其递增到1,导致最终值小于1正确的价值。
如果您的get()
方法不是synchronized
,那么您的增量可能会被正确处理,但其他线程看不到正确发布的i
值。但是这两种方法都是synchronized
,这确保了读写时的内存同步。 synchronized
也执行锁定,以便您可以执行i++
。同样,AtomicInteger
更有效地处理内存同步和增量竞争条件。
更具体地说,当输入synchronized
块时,它会跨越读取内存屏障,这与从volatile
字段读取相同。当退出synchronized
块时,它会越过写入内存屏障,这与写入volatile
字段相同。与synchronized
块的区别在于,还存在锁定以确保一次只能锁定一个特定对象。