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块...我说的对吗?如果这是真的,那么没有synchronized
,atomic.incrementAndGet()
是如何工作的?它是线程安全的吗?
内部阅读和写入volatile变量/原子变量之间有什么区别?我在一些文章中读到线程有一个变量的本地副本-那是什么?
7条答案
按热度按时间insrf1ej1#
你特别询问他们是如何 * 内部工作 * 的,所以你在这里:
不同步
它基本上是从内存中读取值,递增并放回内存。这在单线程中工作,但现在,在多核,多CPU,多级缓存的时代,它将无法正常工作。首先,它引入了竞争条件(多个线程可以同时读取该值),但也存在可见性问题。该值可能仅存储在“local”CPU内存中(一些缓存)并且对于其他CPU/核心不可见(因此- threads)。这就是为什么许多人在线程中引用变量的 local copy。这是非常不安全的。考虑这个流行但不完整的线程停止代码:
将
volatile
添加到stopped
变量中,它可以正常工作-如果任何其他线程通过pleaseStop()
方法修改stopped
变量,您可以保证在工作线程的while(!stopped)
循环中立即看到更改。顺便说一句,这也不是中断线程的好方法,请参阅:How to stop a thread that is running forever without any use和Stopping a specific java thread。AtomicInteger
AtomicInteger
类使用CAS(compare-and-swap)低级CPU操作(不需要同步!)它们允许您仅在当前值等于其他值(并且成功返回)时修改特定变量。因此,当您执行getAndIncrement()
时,它实际上在循环中运行(简化的真实的实现):所以基本上读取;尝试存储递增值;如果不成功(值不再等于
current
),请读取并重试。compareAndSet()
是在本机代码(汇编)中实现的。volatile
不同步这段代码不正确。它修复了可见性问题(
volatile
确保其他线程可以看到对counter
所做的更改),但仍然有一个竞争条件。这已经是explained多次:前/后增量不是原子的。volatile
的唯一副作用是“flushing”缓存,以便所有其他方看到最新版本的数据。这就是为什么volatile
不是默认值。volatile
无同步(2)与上面的问题相同,但更糟糕的是,因为
i
不是private
。竞态条件仍然存在。为什么这是一个问题?如果,比如说,两个线程同时运行此代码,输出可能是+ 5
或+ 10
。但是,您可以保证看到更改。多个独立的
synchronized
令人惊讶的是,这段代码也是不正确的。事实上,它是完全错误的。首先,你正在
i
上同步,它即将被更改(此外,i
是一个原始的,所以我猜你正在通过自动装箱创建的临时Integer
上同步......)完全有缺陷。你也可以这样写:没有两个线程可以使用相同的锁进入相同的
synchronized
块**。在这种情况下(在您的代码中也是如此),锁对象在每次执行时都会发生变化,因此synchronized
实际上没有影响。即使使用了final变量两个线程可以先同步读取
i
到temp
(在temp
中具有相同的本地值),然后第一个线程为i
分配一个新值(比如从1到6),另一个线程做同样的事情(从1到6)。同步必须从阅读到赋值。您的第一次同步没有效果(读取
int
是原子的),第二次同步也没有效果。在我看来,这些是正确的形式:lrpiutwd2#
声明一个变量为volatile意味着修改它的值会立即影响该变量的实际内存存储。编译器无法优化掉对该变量的任何引用。这保证了当一个线程修改该变量时,所有其他线程都会立即看到新值。(对于非volatile变量,这并不保证。)
声明原子变量保证了对该变量的操作是以原子的方式进行的,即操作的所有子步骤都是在它们所执行的线程内完成的,不会被其他线程中断。例如,递增和测试操作需要将变量递增,然后与另一个值进行比较;原子操作保证这两个步骤都将被完成,就好像它们是单个不可分割/不可中断的操作一样。
同步所有对变量的访问,一次只允许单个线程访问该变量,并强制所有其他线程等待该访问线程释放对该变量的访问。
同步访问类似于原子访问,但原子操作通常在较低的编程级别上实现。此外,完全可以仅同步对变量的某些访问,并允许其他访问不同步(例如,同步对变量的所有写入,但不同步对变量的读取)。
原子性、同步性和易失性是独立的属性,但通常组合使用,以实施适当的线程协作来访问变量。
增编*(2016年4月)*
对变量的同步访问通常使用 monitor 或 * 信号量 * 来实现。这些是低级的 mutex(互斥)机制,允许一个线程独占地获取对变量或代码块的控制,如果其他所有线程也试图获取相同的mutex,则强制所有其他线程等待。一旦拥有该mutex的线程释放了mutex,另一个线程就可以依次获取mutex。
增编*(2016年7月)*
同步发生在一个 object 上。这意味着调用一个类的同步方法将锁定调用的
this
对象。静态同步方法将锁定Class
对象本身。同样,进入synchronized块需要锁定方法的
this
对象。这意味着同步方法(或块)可以同时在多个线程中执行,如果它们锁定在不同的对象上,但是对于任何给定的单个对象,一次只有一个线程可以执行同步方法(或块)。
63lcw9qa3#
volatile:
volatile
是一个关键字。volatile
强制所有线程从主内存而不是缓存中获取变量的最新值。所有线程可以同时访问volatile变量值,而不会锁定。它减少了内存一致性错误。
AtomicXXX:
AtomicXXX
类支持对单个变量进行无锁线程安全编程。同步:
synchronized
是用于保护方法或代码块的关键字。通过使方法同步,您可以实现两件事。1.在同一对象上执行两次
synchronized
方法永远不会运行1.对象状态的更改对其他线程可见
AtomicXXX
等同于volatile + synchronized
,尽管实现方式不同。AmtomicXXX
扩展了volatile
变量+compareAndSet
方法,但不使用同步。rqdpfwrv4#
我知道两个线程不能同时进入Synchronize块
两个线程不能两次进入同一个对象上的同步块。这意味着两个线程可以进入不同对象上的同一个块。这种混淆可能导致这样的代码。
这将不会像预期的那样运行,因为它可能每次锁定不同的对象。
如果这是真的,那么这个原子.incrementAndGet()在没有Synchronize的情况下如何工作??并且是线程安全的??
是的。它不使用锁来实现线程安全。
如果你想更详细地了解它们是如何工作的,你可以阅读它们的代码。
内部阅读和写入挥发性变量/原子变量之间有什么区别?
Atomic类使用volatile**字段,**字段没有区别,区别在于执行的操作,Atomic类使用CompareAndSwap或CAS操作。
我在一些文章中读到线程有变量的本地副本,那是什么??
我只能假设它指的是每个CPU都有自己的缓存内存视图,这可能与其他CPU不同。为了确保CPU具有一致的数据视图,您需要使用线程安全技术。
只有当内存被共享时,至少有一个线程更新它,这才是一个问题.
6tqwzwtp5#
同步Vs原子Vs挥发:
请纠正我,如果我错过了什么。
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++周围进行同步,以保持它是防傻瓜的原子语句。记住一个语句包含多个语句的事实。
希望解释够清楚。
gg58donl7#
Javavolatile修饰符是一种特殊机制的例子,它保证了线程之间的通信。当一个线程写一个volatile变量,另一个线程看到写操作时,第一个线程会告诉第二个线程内存中的所有内容,直到它执行了对该volatile变量的写操作。
原子操作在单个任务单元内执行,不受其他操作的干扰。原子操作是多线程环境下避免数据不一致的必要操作。