您特别关注它们的 内部工作方式,因此这里是:
无同步
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
它基本上从内存中读取值,将其增加并放回到内存中。这在单个线程中可以工作,但现今的多核、多 CPU、多级缓存的时代中,它无法正常工作。首先,它会引入竞态条件(多个线程可以同时读取该值),而且可见性问题也会出现。该值可能仅被存储在“本地” CPU 内存(某个缓存)中,而不对其他 CPU/核心(以及因此 - 线程)可见。这就是为什么许多人提到线程中的“本地副本”变量,这非常不安全。考虑以下流行但有缺陷的线程停止代码:
private boolean stopped;
public void run() {
while(!stopped) {
}
}
public void pleaseStop() {
stopped = true;
}
将 volatile
添加到 stopped
变量中,它就可以正常工作了 - 如果任何其他线程通过 pleaseStop()
方法修改 stopped
变量,您保证能够立即在工作线程的 while(!stopped)
循环中看到该变化。顺便说一句,这也不是中断线程的好方法,请参见:如何停止永远运行而没有任何用处的线程 和 停止特定的Java线程。
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
AtomicInteger
类使用 CAS(比较并交换)低级 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
所做的更改),但仍存在竞争条件。这已经在多次解释过:预/后增量不是原子的。
volatile
的唯一副作用是"刷新"缓存,以便所有其他方看到数据的最新版本。在大多数情况下,这太严格了;这就是为什么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即将被更改(此外,i
是一个原始类型,所以我猜你正在同步一个通过自动装箱创建的临时Integer
......)完全有缺陷。你也可以这样写:
synchronized(new Object()) {
}
两个线程不能使用相同的锁进入同一个 synchronized
块。在这种情况下(以及您的代码中类似的情况),每次执行时锁对象都会发生更改,因此 synchronized
实际上没有任何效果。
即使您已经使用了 final 变量(或 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;
}
}