原子/易失性/同步之间有什么区别? [英] What is the difference between atomic / volatile / synchronized?

查看:27
本文介绍了原子/易失性/同步之间有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

atomic/volatile/synchronized 在内部如何工作?

以下代码块有什么区别?

代码 1

私有整数计数器;公共 int getNextUniqueIndex() {返回计数器++;}

代码 2

私有 AtomicInteger 计数器;公共 int getNextUniqueIndex() {返回 counter.getAndIncrement();}

代码 3

私有 volatile int 计数器;公共 int getNextUniqueIndex() {返回计数器++;}

<小时>

volatile 是否按以下方式工作?是

volatile int i = 0;无效 incIBy5() {我 += 5;}

相当于

整数 i = 5;无效 incIBy5() {内部温度;同步(i){临时=我}同步(我){我=温度+ 5}}

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

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

解决方案

您是专门询问他们内部如何工作,所以您在这里:

无同步

私有整数计数器;公共 int getNextUniqueIndex() {返回计数器++;}

它基本上从内存中读取值,增加它并放回内存.这在单线程中有效,但现在,在多核、多 CPU、多级缓存的时代,它无法正常工作.首先它引入了竞争条件(多个线程可以同时读取值),但也引入了可见性问题.该值可能仅存储在local"CPU 内存(某些缓存)中,对其他 CPU/内核(以及线程)不可见.这就是为什么许多人引用线程中变量的本地副本.这是非常不安全的.考虑一下这个流行但已损坏的线程停止代码:

私有布尔值停止;公共无效运行(){而(!停止){//做一些工作}}公共无效请停止(){停止=真;}

volatile 添加到 stopped 变量并且它工作正常 - 如果任何其他线程通过 pleaseStop()stopped 变量code> 方法,您可以保证在工作线程的 while(!stopped) 循环中立即看到更改.顺便说一句,这也不是中断线程的好方法,请参阅:如何停止永远运行而没有任何用途的线程停止特定的 Java 线程.

AtomicInteger

private AtomicInteger counter = new AtomicInteger();公共 int getNextUniqueIndex() {返回 counter.getAndIncrement();}

AtomicInteger 类使用 CAS(compare-and-swap) 低级 CPU 操作(不需要同步!)只有当当前值等于其他值(并成功返回)时,它们才允许您修改特定变量.因此,当您执行 getAndIncrement() 时,它实际上是在循环中运行(简化的实际实现):

int 当前;做 {当前 = 获取();} while(!compareAndSet(current, current + 1));

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

volatile 不同步

私有 volatile int 计数器;公共 int getNextUniqueIndex() {返回计数器++;}

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

volatile 的唯一副作用是刷新"缓存,以便所有其他方都能看到最新版本的数据.这在大多数情况下过于严格;这就是 volatile 不是默认值的原因.

volatile 不同步(2)

volatile int i = 0;无效 incIBy5() {我 += 5;}

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

多个独立同步

void incIBy5() {内部温度;同步(i){临时=我}同步(我){我=温度+ 5}}

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

synchronized(new Object()) {//线程安全,SRSLy?}

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

即使您使用了最终变量(或this)进行同步,代码仍然不正确.两个线程可以先同步读取itemp(temp本地具有相同的值),然后第一个给赋值>i(比如说,从 1 到 6),另一个做同样的事情(从 1 到 6).

同步必须从读取到赋值.您的第一次同步没有效果(读取 int 是原子的),第二次也是如此.在我看来,这些是正确的形式:

void synchronized incIBy5() {我 += 5}无效 incIBy5() {同步(这个){我 += 5}}无效 incIBy5() {同步(这个){int temp = i;我 = 温度 + 5;}}

How do atomic / volatile / synchronized work internally?

What is the difference between the following code blocks?

Code 1

private int counter;

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

Code 2

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}

Code 3

private volatile int counter;

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


Does volatile work in the following way? Is

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

equivalent to

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

I think that two threads cannot enter a synchronized block at the same time... am I right? If this is true then how does atomic.incrementAndGet() work without synchronized? And is it thread-safe?

And what is the difference between internal reading and writing to volatile variables / atomic variables? I read in some article that the thread has a local copy of the variables - what is that?

解决方案

You are specifically asking about how they internally work, so here you are:

No synchronization

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

Add volatile to stopped variable and it works fine - if any other thread modifies stopped variable via pleaseStop() method, you are guaranteed to see that change immediately in working thread's while(!stopped) loop. BTW this is not a good way to interrupt a thread either, see: How to stop a thread that is running forever without any use and Stopping a specific java thread.

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
  return counter.getAndIncrement();
}

The AtomicInteger class uses CAS (compare-and-swap) low-level CPU operations (no synchronization needed!) They allow you to modify a particular variable only if the present value is equal to something else (and is returned successfully). So when you execute getAndIncrement() it actually runs in a loop (simplified real implementation):

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

So basically: read; try to store incremented value; if not successful (the value is no longer equal to current), read and try again. The compareAndSet() is implemented in native code (assembly).

volatile without synchronization

private volatile int counter;

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

This code is not correct. It fixes the visibility issue (volatile makes sure other threads can see change made to counter) but still has a race condition. This has been explained multiple times: pre/post-incrementation is not atomic.

The only side effect of volatile is "flushing" caches so that all other parties see the freshest version of the data. This is too strict in most situations; that is why volatile is not default.

volatile without synchronization (2)

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

The same problem as above, but even worse because i is not private. The race condition is still present. Why is it a problem? If, say, two threads run this code simultaneously, the output might be + 5 or + 10. However, you are guaranteed to see the change.

Multiple independent synchronized

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

Surprise, this code is incorrect as well. In fact, it is completely wrong. First of all you are synchronizing on i, which is about to be changed (moreover, i is a primitive, so I guess you are synchronizing on a temporary Integer created via autoboxing...) Completely flawed. You could also write:

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

No two threads can enter the same synchronized block with the same lock. In this case (and similarly in your code) the lock object changes upon every execution, so synchronized effectively has no effect.

Even if you have used a final variable (or this) for synchronization, the code is still incorrect. Two threads can first read i to temp synchronously (having the same value locally in temp), then the first assigns a new value to i (say, from 1 to 6) and the other one does the same thing (from 1 to 6).

The synchronization must span from reading to assigning a value. Your first synchronization has no effect (reading an int is atomic) and the second as well. In my opinion, these are the correct forms:

void synchronized incIBy5() {
  i += 5 
}

void incIBy5() {
  synchronized(this) {
    i += 5 
  }
}

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

这篇关于原子/易失性/同步之间有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆