您是专门询问他们在内部是如何工作的,所以您在这里:
不同步
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
它基本上从内存中读取值,将其递增并放回内存。这适用于单线程,但现在,在多核、多 CPU、多级缓存的时代,它无法正常工作。首先它引入了竞争条件(多个线程可以同时读取该值),但也引入了可见性问题。该值可能仅存储在“local”CPU 内存(某些缓存)中,而对其他 CPU/核心(因此 - 线程)不可见。这就是为什么许多人在线程中引用变量的本地副本。这是非常不安全的。考虑一下这个流行但损坏的线程停止代码:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
将volatile 添加到stopped 变量,它工作正常 - 如果任何其他线程通过pleaseStop() 方法修改stopped 变量,您可以保证在工作线程的while(!stopped) 循环中立即看到该更改。顺便说一句,这也不是中断线程的好方法,请参阅:How to stop a thread that is running forever without any use 和 Stopping a specific java thread。
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
AtomicInteger 类使用 CAS (compare-and-swap) 低级 CPU 操作(不需要同步!)它们允许您仅在当前值等于其他值时修改特定变量(并且成功返回) .所以当你执行getAndIncrement()时它实际上是在一个循环中运行(简化的实际实现):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
所以基本上:阅读;尝试存储增量值;如果不成功(值不再等于current),请阅读并重试。 compareAndSet() 以本机代码(程序集)实现。
volatile 没有同步
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
此代码不正确。它修复了可见性问题(volatile 确保其他线程可以看到对 counter 所做的更改)但仍然存在竞争条件。这已经explained 多次了:前/后增量不是原子的。
volatile 的唯一副作用是“flushing”缓存,以便所有其他方看到最新版本的数据。在大多数情况下,这太严格了;这就是为什么volatile 不是默认值的原因。
volatile不同步(2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
与上面的问题相同,但更糟糕的是因为i 不是private。竞争条件仍然存在。为什么会出现问题?例如,如果两个线程同时运行此代码,则输出可能是+ 5 或+ 10。但是,您一定会看到变化。
多个独立的synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
惊奇,这个代码也是不正确的。事实上,这是完全错误的。首先,您正在同步i,它即将被更改(此外,i 是一个原语,所以我猜您正在同步通过自动装箱创建的临时Integer...)完全有缺陷。你也可以写:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
没有两个线程可以进入相同的synchronized 块具有相同的锁。在这种情况下(在您的代码中也是如此)锁定对象在每次执行时都会更改,因此synchronized 实际上没有任何效果。
即使您使用了最终变量(或this)进行同步,代码仍然不正确。两个线程可以先同步读取i 到temp(在temp 本地具有相同的值),然后第一个线程为i 分配一个新值(例如,从1 到6),另一个执行相同的东西(从 1 到 6)。
同步必须跨越从读取到赋值。您的第一次同步没有效果(读取int 是原子的),第二次也是如此。在我看来,这些是正确的形式:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}