Java 是否有必要使基元实例变量可变?
为了试验多线程概念,我正在实现我自己的AtomicInteger版本,它使用悲观锁。它看起来像这样:Java 是否有必要使基元实例变量可变?,java,multithreading,volatile,Java,Multithreading,Volatile,为了试验多线程概念,我正在实现我自己的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 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 100000次。我所看到的是,所有增量都正确发生,整数的值正好是(线程数)*(每个线程的增量数)
,即使我没有在基本实例变量I
上使用volatile。我预计,如果我没有将I
设置为volatile,那么我会遇到很多可见性问题,例如,线程1将I
从0增加到1,但是线程2仍然看到0的值,并且只将其增加到1
,导致最终值小于正确值
我知道可见性问题是随机发生的,并且可能取决于我的环境的属性,因此即使存在固有的可见性问题,我的测试也可以正常工作。所以我倾向于认为volatile关键字仍然是必要的
但这是正确的吗?或者我的代码中是否有一些属性(可能只是一个基本变量,等等),我可以信任这些属性来避免使用volatile关键字
即使我没有在原语实例变量I上使用volatile。我希望如果我不让我变得易变,那么我会遇到很多可见性问题
通过使getAndIncrement()
和get()
方法synchronized
,所有正在修改i
的线程都将正确地锁定它以进行更新和检索值。synchronized
块使得i
不必是volatile
,因为它们也确保了内存同步
也就是说,您应该使用一个AtomicInteger
,它包装了一个volatile int
字段AtomicInteger
getAndIncrement()
方法更新值,而不必求助于同步的
块,该块在保持线程安全的同时速度更快
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,导致最终值小于正确值
如果您的get()
方法未synchronized
,那么您的增量可能会得到正确处理,但其他线程将无法正确发布i
的值。但由于这两种方法都是同步的,这确保了读写时的内存同步synchronized
还执行锁定,以便您可以执行i++
。同样,AtomicInteger
处理内存同步和增量竞争条件的效率更高
更具体地说,当进入
同步
块时,它会跨越一个读取存储器屏障,这与从易失性
字段读取相同。当退出synchronized
块时,它会跨越写入内存障碍,这与写入volatile
字段相同。与synchronized
块的不同之处在于,还有一个锁定,以确保一次只有一个人锁定特定对象。您是说synchronized不仅保证代码路径一次只能由一个线程执行,但除此之外,它还保证在同步块内部接触的任何变量都被视为不稳定的?我认为,即使使用synchronize,两个线程也有可能观察到变量中的不同值-因此我认为您的意思是,情况并非如此。@russell这真正的意思是,I++
通常不是原子操作,但由于synchronized
而使其成为原子操作。“@russel当写入和读取方法都同步时,就像在代码中一样,普通字段变量被视为易失性变量。学习“Java内存模型”以开发有意义的测试程序。我在问题@russell的末尾添加了一些内容,以解释内存障碍以及volatile
和synchronized
之间的相似性(和差异)。