原子/挥发/同步内部是如何工作的?

下面的代码块有什么区别?

代码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 int i = 0;
void incIBy5() {
    i += 5;
}

相当于

Integer i = 5;
void incIBy5() {
    int temp;
    synchronized(i) { temp = i }
    synchronized(i) { i = temp + 5 }
}

我认为两个线程不能同时进入同步块…我说的对吗?如果这是真的,那么如何atomic.incrementAndGet()工作没有同步?它是否线程安全?

内部读取和写入volatile变量/原子变量之间的区别是什么?我在一些文章中读到,线程有一个变量的本地副本-那是什么?


当前回答

你特别问了他们内部是如何运作的,所以你是这样的:

没有同步

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

It basically reads value from memory, increments it and puts back to memory. This works in single thread but nowadays, in the era of multi-core, multi-CPU, multi-level caches it won't work correctly. First of all it introduces race condition (several threads can read the value at the same time), but also visibility problems. The value might only be stored in "local" CPU memory (some cache) and not be visible for other CPUs/cores (and thus - threads). This is why many refer to local copy of a variable in a thread. It is very unsafe. Consider this popular but broken thread-stopping code:

private boolean stopped;

public void run() {
    while(!stopped) {
        //do some work
    }
}

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不是默认值。

不同步易变(2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

同样的问题,但更糟糕的是,因为i不是私有的。竞态条件仍然存在。为什么这是个问题?如果两个线程同时运行这段代码,那么输出可能是+ 5或+ 10。但是,您一定会看到变化。

多独立同步

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

令人惊讶的是,这段代码也是不正确的。事实上,这是完全错误的。首先,你正在同步i,这是即将被改变的(此外,i是一个原语,所以我猜你正在同步一个通过自动装箱创建的临时整数…)完全有缺陷的。你也可以这样写:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

没有两个线程可以使用相同的锁进入相同的同步块。在这种情况下(在您的代码中也类似),锁对象在每次执行时都会改变,因此同步实际上没有任何影响。

即使您使用了最终变量(或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;
  }
}

其他回答

同步Vs原子Vs Volatile:

Volatile and Atomic is apply only on variable , While Synchronized apply on method. Volatile ensure about visibility not atomicity/consistency of object , While other both ensure about visibility and atomicity. Volatile variable store in RAM and it’s faster in access but we can’t achive Thread safety or synchronization whitout synchronized keyword. Synchronized implemented as synchronized block or synchronized method while both not. We can thread safe multiple line of code with help of synchronized keyword while with both we can’t achieve the same. Synchronized can lock the same class object or different class object while both can’t.

如有遗漏,请指正。

Java volatile修饰符是一种特殊机制的例子,它可以保证线程之间进行通信。当一个线程写入一个易失性变量,而另一个线程看到这个写入,第一个线程就会告诉第二个线程关于内存中的所有内容,直到它执行对该易失性变量的写入。

原子操作在单个任务单元中执行,不受其他操作的干扰。在多线程环境中,原子操作是避免数据不一致的必要手段。

volatile +同步是一个完全原子化的操作(语句)的可靠解决方案,它包含了对CPU的多条指令。

Say for eg:volatile int i = 2; i++, which is nothing but i = i + 1; which makes i as the value 3 in the memory after the execution of this statement. This includes reading the existing value from memory for i(which is 2), load into the CPU accumulator register and do with the calculation by increment the existing value with one(2 + 1 = 3 in accumulator) and then write back that incremented value back to the memory. These operations are not atomic enough though the value is of i is volatile. i being volatile guarantees only that a SINGLE read/write from memory is atomic and not with MULTIPLE. Hence, we need to have synchronized also around i++ to keep it to be fool proof atomic statement. Remember the fact that a statement includes multiple statements.

希望这个解释足够清楚。

你特别问了他们内部是如何运作的,所以你是这样的:

没有同步

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

It basically reads value from memory, increments it and puts back to memory. This works in single thread but nowadays, in the era of multi-core, multi-CPU, multi-level caches it won't work correctly. First of all it introduces race condition (several threads can read the value at the same time), but also visibility problems. The value might only be stored in "local" CPU memory (some cache) and not be visible for other CPUs/cores (and thus - threads). This is why many refer to local copy of a variable in a thread. It is very unsafe. Consider this popular but broken thread-stopping code:

private boolean stopped;

public void run() {
    while(!stopped) {
        //do some work
    }
}

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不是默认值。

不同步易变(2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

同样的问题,但更糟糕的是,因为i不是私有的。竞态条件仍然存在。为什么这是个问题?如果两个线程同时运行这段代码,那么输出可能是+ 5或+ 10。但是,您一定会看到变化。

多独立同步

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

令人惊讶的是,这段代码也是不正确的。事实上,这是完全错误的。首先,你正在同步i,这是即将被改变的(此外,i是一个原语,所以我猜你正在同步一个通过自动装箱创建的临时整数…)完全有缺陷的。你也可以这样写:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

没有两个线程可以使用相同的锁进入相同的同步块。在这种情况下(在您的代码中也类似),锁对象在每次执行时都会改变,因此同步实际上没有任何影响。

即使您使用了最终变量(或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;
  }
}

我知道两个线程不能同时进入同步块

两个线程不能两次进入同一个对象的同步块。这意味着两个线程可以在不同的对象上进入相同的块。这种混乱可能导致这样的代码。

private Integer i = 0;

synchronized(i) {
   i++;
}

这不会像预期的那样,因为它可能每次都锁定在不同的对象上。

如果这是真的,那么这个atomic.incrementAndGet()如何在没有同步的情况下工作??线程安全吗??

是的。它不使用锁来实现线程安全。

如果您想更详细地了解它们的工作原理,可以阅读它们的代码。

内部读取和写入Volatile变量/原子变量之间的区别是什么?

原子类使用volatile字段。在这个领域没有区别。区别在于所执行的操作。Atomic类使用CompareAndSwap或CAS操作。

我在一些文章中读到线程有变量的本地副本,那是什么?

我只能假设它指的是每个CPU都有自己的缓存内存视图,这可能与其他CPU不同。为了确保CPU拥有一致的数据视图,您需要使用线程安全技术。

这只在共享内存且至少有一个线程更新内存时才会出现问题。