原子/易失/同步如何在内部工作?
以下代码块有什么区别?
代码 1
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
代码 2
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
代码 3
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
是否
volatile
按以下方式工作?是volatile int i = 0;
void incIBy5() {
i += 5;
}
相当于
Integer i = 5;
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
我认为两个线程不能同时进入一个同步块(synchronized block)......我对吗?如果这是真的,那么
atomic.incrementAndGet()
怎么办?无需工作 synchronized
?它是线程安全的吗?内部读取和写入 volatile 变量/原子变量有什么区别?我在一些文章中读到该线程具有变量的本地副本 - 那是什么?
最佳答案
您特别询问他们的内部工作方式,所以您在这里:
不同步
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
它基本上从内存中读取值,增加它并放回内存。这在单线程中有效,但现在,在多核、多 CPU、多级缓存的时代,它无法正常工作。首先它引入了竞争条件(多个线程可以同时读取值),但也引入了可见性问题。该值可能仅存储在“本地”CPU 内存(某些缓存)中,对其他 CPU/内核(以及线程)不可见。这就是为什么许多人在线程中引用变量的本地副本。这是非常不安全的。考虑这个流行但损坏的线程停止代码:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
添加
volatile
至 stopped
变量并且它工作正常 - 如果任何其他线程修改 stopped
变量通过 pleaseStop()
方法,您可以保证立即在工作线程的 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()
在 native 代码(程序集)中实现。volatile
不同步private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
此代码不正确。它修复了可见性问题(
volatile
确保其他线程可以看到对 counter
所做的更改)但仍然存在竞争条件。这是explained多次:前/后增量不是原子的。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
是一个原语,所以我猜您正在同步通过自动装箱创建的临时 Integer
...)完全有缺陷。你也可以这样写:synchronized(new Object()) {
//thread-safe, SRSLy?
}
没有两个线程可以进入同一个
synchronized
区 block 同款锁 .在这种情况下(和你的代码类似),每次执行时锁对象都会改变,所以 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;
}
}
关于java - 原子/ volatile /同步之间有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9749746/