java Atomic / volatile / synchronized有什么区别?

0aydgbwb  于 2023-04-04  发布在  Java
关注(0)|答案(7)|浏览(175)

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

我认为两个线程不能同时进入一个synchronized块...我说的对吗?如果这是真的,那么没有synchronizedatomic.incrementAndGet()是如何工作的?它是线程安全的吗?
内部阅读和写入volatile变量/原子变量之间有什么区别?我在一些文章中读到线程有一个变量的本地副本-那是什么?

insrf1ej

insrf1ej1#

你特别询问他们是如何 * 内部工作 * 的,所以你在这里:

不同步

private int counter;

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

它基本上是从内存中读取值,递增并放回内存。这在单线程中工作,但现在,在多核,多CPU,多级缓存的时代,它将无法正常工作。首先,它引入了竞争条件(多个线程可以同时读取该值),但也存在可见性问题。该值可能仅存储在“local”CPU内存中(一些缓存)并且对于其他CPU/核心不可见(因此- threads)。这就是为什么许多人在线程中引用变量的 local copy。这是非常不安全的。考虑这个流行但不完整的线程停止代码:

private boolean stopped;

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

public void pleaseStop() {
    stopped = true;
}

volatile添加到stopped变量中,它可以正常工作-如果任何其他线程通过pleaseStop()方法修改stopped变量,您可以保证在工作线程的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()是在本机代码(汇编)中实现的。

volatile不同步

private volatile int counter;

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

这段代码不正确。它修复了可见性问题(volatile确保其他线程可以看到对counter所做的更改),但仍然有一个竞争条件。这已经是explained多次:前/后增量不是原子的。
volatile的唯一副作用是“flushing”缓存,以便所有其他方看到最新版本的数据。这就是为什么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实际上没有影响。
即使使用了final变量两个线程可以先同步读取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;
  }
}
lrpiutwd

lrpiutwd2#

声明一个变量为volatile意味着修改它的值会立即影响该变量的实际内存存储。编译器无法优化掉对该变量的任何引用。这保证了当一个线程修改该变量时,所有其他线程都会立即看到新值。(对于非volatile变量,这并不保证。)
声明原子变量保证了对该变量的操作是以原子的方式进行的,即操作的所有子步骤都是在它们所执行的线程内完成的,不会被其他线程中断。例如,递增和测试操作需要将变量递增,然后与另一个值进行比较;原子操作保证这两个步骤都将被完成,就好像它们是单个不可分割/不可中断的操作一样。

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

同步访问类似于原子访问,但原子操作通常在较低的编程级别上实现。此外,完全可以仅同步对变量的某些访问,并允许其他访问不同步(例如,同步对变量的所有写入,但不同步对变量的读取)。
原子性、同步性和易失性是独立的属性,但通常组合使用,以实施适当的线程协作来访问变量。

增编*(2016年4月)*

对变量的同步访问通常使用 monitor 或 * 信号量 * 来实现。这些是低级的 mutex(互斥)机制,允许一个线程独占地获取对变量或代码块的控制,如果其他所有线程也试图获取相同的mutex,则强制所有其他线程等待。一旦拥有该mutex的线程释放了mutex,另一个线程就可以依次获取mutex。

增编*(2016年7月)*

同步发生在一个 object 上。这意味着调用一个类的同步方法将锁定调用的this对象。静态同步方法将锁定Class对象本身。
同样,进入synchronized块需要锁定方法的this对象。
这意味着同步方法(或块)可以同时在多个线程中执行,如果它们锁定在不同的对象上,但是对于任何给定的单个对象,一次只有一个线程可以执行同步方法(或块)。

63lcw9qa

63lcw9qa3#

volatile:

volatile是一个关键字。volatile强制所有线程从主内存而不是缓存中获取变量的最新值。所有线程可以同时访问volatile变量值,而不会锁定。
它减少了内存一致性错误。

  • 何时用途:一个线程修改数据,其他线程读取最新的数据值。其他线程将采取一些行动,而不更新数据 *。
    AtomicXXX:

AtomicXXX类支持对单个变量进行无锁线程安全编程。

  • 何时用途:多个线程可以读取和修改数据。*
    同步:

synchronized是用于保护方法或代码块的关键字。通过使方法同步,您可以实现两件事。
1.在同一对象上执行两次synchronized方法永远不会运行
1.对象状态的更改对其他线程可见

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

AtomicXXX等同于volatile + synchronized,尽管实现方式不同。
AmtomicXXX扩展了volatile变量+ compareAndSet方法,但不使用同步。

rqdpfwrv

rqdpfwrv4#

我知道两个线程不能同时进入Synchronize块
两个线程不能两次进入同一个对象上的同步块。这意味着两个线程可以进入不同对象上的同一个块。这种混淆可能导致这样的代码。

private Integer i = 0;

synchronized(i) {
   i++;
}

这将不会像预期的那样运行,因为它可能每次锁定不同的对象。
如果这是真的,那么这个原子.incrementAndGet()在没有Synchronize的情况下如何工作??并且是线程安全的??
是的。它不使用锁来实现线程安全。
如果你想更详细地了解它们是如何工作的,你可以阅读它们的代码。
内部阅读和写入挥发性变量/原子变量之间有什么区别?
Atomic类使用volatile**字段,**字段没有区别,区别在于执行的操作,Atomic类使用CompareAndSwap或CAS操作。
我在一些文章中读到线程有变量的本地副本,那是什么??
我只能假设它指的是每个CPU都有自己的缓存内存视图,这可能与其他CPU不同。为了确保CPU具有一致的数据视图,您需要使用线程安全技术。
只有当内存被共享时,至少有一个线程更新它,这才是一个问题.

6tqwzwtp

6tqwzwtp5#

同步Vs原子Vs挥发:

  • Volatile和Atomic仅适用于变量,而Synchronized适用于方法。
  • Volatile保证对象的可见性而不是原子性/一致性,而其他的既保证对象的可见性又保证对象的原子性。
  • 易失性变量存储在RAM中,访问速度更快,但如果没有synchronized关键字,就无法实现线程安全或同步。
  • Synchronized实现为synchronized块或synchronized方法,但两者都不是。我们可以在synchronized关键字的帮助下实现线程安全的多行代码,但两者都不能实现相同的功能。
  • Synchronized可以锁定同一个类对象或不同的类对象,但两者都不能。

请纠正我,如果我错过了什么。

p5cysglq

p5cysglq6#

volatile + synchronization是一种防傻瓜的解决方案,用于使操作(语句)完全原子化,其中包括对CPU的多个指令。
例如:volatile int i = 2;i++,它只不过是i = i + 1;这使得i在执行此语句后在内存中的值为3。这包括从内存中阅读i的现有值(即2),加载到CPU累加器寄存器中,并通过将现有值递增1来进行计算(在累加器中2 + 1 = 3),然后将递增值写回到存储器。这些操作不是足够原子的,尽管i的值是volatile。i是volatile仅保证从存储器的单个读/写是原子的,而不是具有MULTIPLE。我们还需要在i++周围进行同步,以保持它是防傻瓜的原子语句。记住一个语句包含多个语句的事实。
希望解释够清楚。

gg58donl

gg58donl7#

Javavolatile修饰符是一种特殊机制的例子,它保证了线程之间的通信。当一个线程写一个volatile变量,另一个线程看到写操作时,第一个线程会告诉第二个线程内存中的所有内容,直到它执行了对该volatile变量的写操作。

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

相关问题