atomic / volatile / synchronized有什么区别? [英] What is the difference between atomic / volatile / synchronized?

查看:155
本文介绍了atomic / volatile / synchronized有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How do atomic / volatile / synchronized work internally?

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

What is the difference between the following code blocks?

代码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 的情况下工作?它是线程安全的吗?

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?

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

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:

private int counter;

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

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

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 已停止变量并且工作正常 - 如果任何其他线程修改已停止变量通过 pleaseStop() 方法,保证在工作线程的中立即看到更改,同时(!停止)循环。顺便说一句,这不是打断线程的好方法,请参阅:如何停止永久运行而没有任何使用的线程停止特定的java帖子

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.

private AtomicInteger counter = new AtomicInteger();

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

AtomicInteger 类使用CAS(比较和交换)低级CPU操作(无同步)需要!)它们允许您仅在当前值等于其他值时修改特定变量(并且成功返回)。所以当你执行 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));

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

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).

private volatile int counter;

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

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

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.

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

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

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

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.

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

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

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

没有两个线程可以输入相同的 synchronized 使用相同的锁定阻止 。在这种情况下(类似地在你的代码中)锁对象在每次执行时都会改变,所以 synchronized 实际上没有效果。

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.

即使您使用了最终变量(或 this )进行同步,代码仍然不正确。两个线程可以先同步读取 i temp (在 temp <中本地具有相同的值/ code>),然后第一个将新值分配给 i (例如,从1到6),另一个做同样的事情(从1到1) 6)。

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).

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

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

这篇关于atomic / volatile / synchronized有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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