java - 原子/ volatile /同步之间有什么区别?

标签 java multithreading synchronization atomic volatile

原子/易失/同步如何在内部工作?

以下代码块有什么区别?

代码 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;
}

添加 volatilestopped变量并且它工作正常 - 如果任何其他线程修改 stopped变量通过 pleaseStop()方法,您可以保证立即在工作线程的 while(!stopped) 中看到更改环形。顺便说一句,这也不是中断线程的好方法,请参阅:How to stop a thread that is running forever without any useStopping 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 )进行同步,代码仍然不正确。两个线程可以先读itemp同步(在 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/

相关文章:

java - Android:HttpURLConnection 还是套接字?

java - Java中的ScheduledExecutorService和线程

java - 构建受控的java线程队列

multithreading - Delphi MREW 实现对读者有利吗?

java - 如何修复在 OpenGL 中沿 x 或 y 轴旋转时奇怪的对象拉伸(stretch)?

java - 为什么 spring boot angularjs gateway 应用程序不能从 ui 应用程序读取?

java - 为什么从 HashMap 返回的 String 与 System.out 相同但不匹配?

java - ScheduledExecutorService 的不稳定行为

go - 取消 HTTP 请求时关闭所有 goroutine

java - 两个线程执行完毕后进行清理