是否有必要使原始实例变量可变?
Is it necessary to make a primitive instance variable 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 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 关键字的需要?
even though I'm not using volatile on the primitive instance variable i. I expected that if I did not make i volatile, then I would get lots of visibility problems
通过使您的 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();
}
I would get lots of visibility problems where, for instance, thread 1 increments i from 0 to 1, but thread 2 still sees the value of 0 and also increments it to only 1, causing a final value that is less than the correct value.
如果您的 get()
方法不是 synchronized
,那么您的增量可能会得到正确处理,但其他线程不会看到 i
的值被正确发布。但是这两种方法都是 synchronized
这确保了读写时的内存同步。 synchronized
也进行锁定,以便您可以进行 i++
。同样,AtomicInteger
更有效地处理内存同步和增量竞争条件。
更具体地说,当输入 synchronized
块时,它会跨越读取内存屏障,这与从 volatile
字段读取相同。当退出 synchronized
块时,它会跨越写入内存屏障,这与写入 volatile
字段相同。 synchronized
块的不同之处在于,还有锁定以确保一次只有一个人锁定特定对象。
为了试验多线程概念,我正在实现我自己的使用悲观锁定的 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 关键字的需要?
even though I'm not using volatile on the primitive instance variable i. I expected that if I did not make i volatile, then I would get lots of visibility problems
通过使您的 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();
}
I would get lots of visibility problems where, for instance, thread 1 increments i from 0 to 1, but thread 2 still sees the value of 0 and also increments it to only 1, causing a final value that is less than the correct value.
如果您的 get()
方法不是 synchronized
,那么您的增量可能会得到正确处理,但其他线程不会看到 i
的值被正确发布。但是这两种方法都是 synchronized
这确保了读写时的内存同步。 synchronized
也进行锁定,以便您可以进行 i++
。同样,AtomicInteger
更有效地处理内存同步和增量竞争条件。
更具体地说,当输入 synchronized
块时,它会跨越读取内存屏障,这与从 volatile
字段读取相同。当退出 synchronized
块时,它会跨越写入内存屏障,这与写入 volatile
字段相同。 synchronized
块的不同之处在于,还有锁定以确保一次只有一个人锁定特定对象。