atomic / volatile / synchronized有什么区别?

问题

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

以下代码块之间有什么区别?

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

136861608是按以下方式工作的吗?是

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()如何没有synchronized?它是线程安全的吗?

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


#1 热门回答(311 赞)

你特别询问他们的内部工作方式,所以你在这里:

#没有同步

private int counter;

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

它基本上从内存中读取值,递增它并放回内存。这在单线程中工作,但是现在,在多核,多CPU,多级缓存的时代,它将无法正常工作。首先,它介绍了竞争条件(几个线程可以同时读取值),但也有可见性问题。该值可能只存储在"本地"CPU内存(某些缓存)中,而对于其他CPU /内核(因此 - 线程)则不可见。这就是为什么许多人在线程中引用变量的tolocal副本。这是非常不安全的。考虑这个流行但破坏的线程停止代码:

private boolean stopped;

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

public void pleaseStop() {
    stopped = true;
}

Addvolatiletostopped变量,它工作正常 - 如果任何其他线程通过875576​​598方法修改stopped变量,你可以保证在工作线程'while(!stopped)loop中立即看到该更改。 BTW这也不是打断线程的好方法,参见: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();
}

TheAtomicIntegerclass使用CAS(compare-and-swap)低级CPU操作(不需要同步!)它们允许你仅在当前值等于其他值时修改特定变量(并且成功返回)。所以当你执行getAndIncrement()it实际上是在一个循环中运行(简化的实际实现):

int current;
do {
  current = get();
} while(!compareAndSet(current, current + 1));

所以基本上:阅读;尝试存储增量值;如果不成功(该值不再等于current),请阅读并重试。 ThecompareAndSet()在本机代码(程序集)中实现。

#molatile不同步

private volatile int counter;

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

此代码不正确。它修复了可见性问题(volatile确保其他线程可以看到对counter所做的更改)但仍然存在竞争条件。这已经是1916161086次:前/后递增不是原子的。

volatile的唯一副作用是"刷新"缓存,以便所有其他方看到最新版本的数据。在大多数情况下,这太严格了;这就是为什么volatile不是默认的。

#molatile without synchronization(2)

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

与上面相同的问题,但更糟糕的是因为i不是private。竞争条件仍然存在。为什么这是一个问题?例如,如果两个线程同时运行此代码,则输出可能是+ 5+ 10。但是,你可以保证看到更改。

#多个独立同步

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

很惊讶,这段代码也不正确。事实上,这是完全错误的。首先你正在同步oni,即将被更改(此外,i是一个原始的,所以我猜你正在通过autoboxing创建临时Integer同步......)完全有缺陷。你也可以这样写:

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

没有两个线程可以使用相同的锁输入相同的synchronizedblock。在这种情况下(类似地在你的代码中)锁定对象在每次执行时都会更改,因此synchronized无效。

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

#2 热门回答(39 赞)

声明变量as​​volatile意味着修改其值会立即影响变量的实际内存存储。编译器无法优化对变量的任何引用。这保证了当一个线程修改变量时,所有其他线程立即看到新值。 (对于非易失性变量,这不保证。)

声明an原子变量保证对变量进行的操作以原子方式发生,即操作的所有子步骤在它们被执行的线程内完成并且不被其他线程中断。例如,增量和测试操作要求增加变量,然后与另一个值进行比较;原子操作保证这两个步骤都将完成,就像它们是一个不可分割/不可中断的操作一样。

同步对变量的所有访问一次只允许一个线程访问该变量,并强制所有其他线程等待该访问线程释放其对该变量的访问权。

同步访问类似于原子访问,但原子操作通常在较低级别的编程中实现。此外,完全可以仅对变量的某些访问进行同步,并允许其他访问不同步(例如,将所有写入同步到变量但不从其中读取任何内容)。

原子性,同步性和波动性是独立属性,但通常组合使用以强制执行适当的线程协作以访问变量。

附录(2016年4月)

通常使用amonitororsemaphore实现对变量的同步访问。这些是低级互斥(互斥)机制,允许线程专门获取对变量或代码块的控制,强制所有其他线程等待,如果它们也尝试获取相同的互斥锁。一旦拥有线程释放互斥锁,另一个线程就可以依次获取互斥锁。

附录(2016年7月)

在对象上进行同步。这意味着调用类的同步方法将锁定调用的对象。静态同步方法将锁定Class对象本身。

同样,输入同步块需要锁定方法的对象.357671743对象。

这意味着同步方法(或块)可以在多个线程中同时执行,如果它们锁定不同的对象,但是对于任何给定的对象,一次只有一个线程可以执行同步方法(或块)。


#3 热门回答(9 赞)

挥发性:
volatile是一个关键字.volatile强制所有线程从主内存而不是缓存中获取变量的最新值。访问volatile变量不需要锁定。所有线程可以同时访问volatile变量值。

使用volatile变量可降低内存一致性错误的风险,因为对volatile变量的任何写入都会建立与之后读取同一变量的先发生关系。

这意味着对其他线程始终可以看到对avolatile变量的更改。更重要的是,它还意味着当线程读取avolatile变量时,它不仅会看到对volatile的最新更改,还会看到导致更改的代码的副作用。

何时使用:一个线程修改数据,其他线程必须读取最新的数据值。其他线程将采取一些行动,但他们不会更新数据。
AtomicXXX:
AtomicXXXclasses支持对单个变量进行无锁线程安全编程。这些AtomicXXX类(如AtomicInteger)解决了在多个线程中访问的volatile变量修改的内存不一致错误/副作用。

何时使用:多个线程可以读取和修改数据。
同步:
synchronized是用于保护方法或代码块的关键字。通过使方法同步有两个效果:

  • 首先,对同一对象的两个同步方法的调用不可能进行交错。当一个线程正在为对象执行同步方法时,所有其他线程都会调用同一对象的同步方法(暂停执行),直到第一个线程完成对象为止。
  • 其次,当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立先发生关系。这可以保证对所有线程都可以看到对象状态的更改。

何时使用:多个线程可以读取和修改数据。你的业​​务逻辑不仅更新数据,还执行原子操作

AtomicXXX相当于volatile + synchronized,尽管实现方式不同.AmtomicXXXextendsvolatilevariables compareAndSet方法但不使用同步。

相关的SE问题:
Difference between volatile and synchronized in JavaVolatile boolean vs AtomicBoolean
好文章阅读:(以上内容摘自这些文档页面)
https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.htmlhttps://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.htmlhttps://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html