你是在问他们是怎么做到的,所以给你:
No synchronization
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
它基本上是从内存中读取值,增加值,然后放回内存.它可以在单线程中工作,但如今,在多核、多CPU、多级缓存的时代,它无法正常工作.首先,它引入了竞争条件(多个线程可以同时读取值),但也存在可见性问题.该值可能只存储在"local"CPU内存(一些缓存)中,对于其他CPU/内核(以及线程)不可见.这就是为什么很多人会引用线程中变量的local copy.这是非常不安全的.考虑这个流行但中 break line 程停止代码:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
将volatile
添加到stopped
变量,它就可以正常工作了——如果任何其他线程通过pleaseStop()
方法修改stopped
变量,那么可以保证在工作线程的while(!stopped)
循环中立即看到这种变化.顺便说一句,这也不是中 break line 程的好方法,请参见: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));
因此基本上是:读取;try 存储递增的值;如果不成功(该值不再等于current
),则读取并重试.compareAndSet()
以本机代码(汇编)实现.
volatile
without synchronization
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
此代码不正确.它修复了可见性问题(volatile
确保其他线程可以看到对counter
所做的更改),但仍然存在争用条件.这已经是explained多次了:前/后增量不是原子的.
volatile
的唯一副作用是"flushing"缓存,以便所有其他方都能看到最新版本的数据.这在大多数情况下过于严格;这就是为什么volatile
不是默认值.
volatile
without synchronization (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
与上面的问题相同,但更糟糕的是,i
不是private
.比赛条件仍然存在.为什么会有问题?例如,如果两个线程同时运行此代码,则输出可能是+ 5
或+ 10
.然而,你肯定会看到变化.
Multiple independent synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
令人惊讶的是,这个代码也不正确.事实上,这是完全错误的.首先,您正在i
上同步,这将被更改(此外,i
是一个原语,所以我猜您正在通过自动装箱创建的临时Integer
上同步…)完全有缺陷.你也可以写:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
没有两个线程可以进入同一个synchronized
挡路with the same lock.在这种情况下(与您的代码类似),每次执行时锁对象都会更改,因此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;
}
}