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

以下列方式进行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 }
}

我认为两个线程不能同时进入一个同步块...我是对的吗? 如果这是真的,那么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变量中,并且工作正常 - 如果其他线程通过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不是默认值。

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块。 在这种情况下(同样在你的代码中)锁对象在每次执行时都会改变,所以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;
  }
}

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

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

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

同步访问类似于原子访问,但是原子操作通常在较低级别的编程中实现。 另外,完全可以将一些访问同步到一个变量,并允许其他访问不同步(例如,将所有写入同步到一个变量,但不同步读取)。

原子性,同步性和波动性是独立的属性,但通常组合使用以实现访问变量的正确线程合作。

附录 (2016年4月)

同步访问变量通常使用监视器或信号量来实现。 这些是低级互斥体(互斥)机制,它允许线程专门获取对变量或代码块的控制权,如果所有其他线程也试图获取相同的互斥体,则强制所有其他线程等待。 一旦拥有线程释放互斥量,另一个线程可以轮流获取互斥量。

附录 (2016年7月)

同步发生在一个对象上。 这意味着调用类的同步方法将锁定调用的this对象。 静态同步方法将锁定Class对象本身。

同样,输入一个同步块需要锁定该方法的this对象。

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


挥发性:

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

使用volatile变量可以降低内存一致性错误的风险,因为任何对volatile变量的写入都会与随后的同一个变量的读取之间建立一个happen-before关系。

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

何时使用:一个线程修改数据,其他线程必须读取最新的数据值。 其他线程将采取一些措施,但不会更新数据。

AtomicXXX:

AtomicXXX类支持单变量的无锁线程安全编程。 这些AtomicXXX类(如AtomicInteger )解决了在多线程中访问的volatile变量修改的内存不一致性错误/副作用。

何时使用:多个线程可以读取和修改数据。

同步:

synchronized是用于保护方法或代码块的关键字。 通过使方法同步有两个作用:

  • 首先,对同一对象上的两个synchronized方法的调用不可能交错。 当一个线程正在执行一个对象的synchronized方法时,所有其他线程调用同一对象的synchronized方法块(挂起执行),直到第一个线程完成对象。

  • 其次,当一个synchronized方法退出时,它会自动建立与同一对象的任何后续synchronized方法的调用之间的一个前后关系。 这保证了对所有线程都可见的对象状态的更改。

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

    即使实现不同, AtomicXXX也相当于volatile + synchronizedAmtomicXXX扩展了volatile变量+ compareAndSet方法,但不使用同步。

    相关的SE问题:

    Java中volatile和synchronized之间的区别

    易失性布尔与AtomicBoolean

    好文章阅读:(以上内容摘自这些文档页面)

    https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

    https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

    https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html

    链接地址: http://www.djcxy.com/p/76243.html

    上一篇: What is the difference between atomic / volatile / synchronized?

    下一篇: Equivalent code for instance method synchronization in Java