atomic / volatile / synchronized有什么区别?
primefaces/易失性/同步如何在内部工作?
以下代码块有什么区别?
代码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
情况下工作? 它是线程安全的吗?
内部读取和写入volatilevariables/primefacesvariables有什么区别? 我在一些文章中读到,线程有一个variables的本地副本 – 那是什么?
你具体询问他们是如何在内部工作的 ,所以你在这里:
没有同步
private int counter; public int getNextUniqueIndex() { return counter++; }
它基本上从内存中读取值,增加它并放回内存。 这在单线程下工作,但是现在在多核,多CPU,多级caching的时代,它将不能正常工作。 首先它引入竞争条件(多个线程可以同时读取值),还有可见性问题。 该值可能只存储在“ 本地 ”CPU内存(某些caching)中,而对其他CPU /内核(以及线程)不可见。 这就是为什么许多引用线程中的variables的本地副本 。 这是非常不安全的。 考虑一下这个stream行但破坏的线程停止代码:
private boolean stopped; public void run() { while(!stopped) { //do some work } } public void pleaseStop() { stopped = true; }
将volatile
添加到已stopped
variables中,并且工作正常 – 如果其他线程通过pleaseStop()
方法修改stopped
variables,则可以保证在工作线程的while(!stopped)
循环中立即看到该变化。 顺便说一句,这不是一个中断一个线程的好方法,请参阅: 如何停止一个没有任何使用的永久运行的线程,并停止一个特定的Java线程 。
AtomicInteger
private AtomicInteger counter = new AtomicInteger(); public int getNextUniqueIndex() { return counter.getAndIncrement(); }
AtomicInteger
类使用CAS( 比较和交换 )低级CPU操作(不需要同步!)它们允许您只在当前值等于其他值(并成功返回)时修改特定variables。 所以当你执行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
),但仍然存在竞争条件。 这已被多次解释 :前/后增量不是primefaces的。
volatile
的唯一副作用是“ 冲洗 ”caching,以便所有其他方都能看到最新版本的数据。 在大多数情况下这是严格的。 这就是为什么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
创build通过自动装箱…)完全有缺陷。 你也可以写:
synchronized(new Object()) { //thread-safe, SRSLy? }
没有两个线程可以使用相同的锁进入同一个synchronized
块。 在这种情况下(和你的代码类似),锁对象在每次执行时都会改变,所以synchronized
有效无效。
即使您已经使用最终variables(或this
)进行同步,代码仍然不正确。 两个线程可以首先同步读取temp
(在temp
具有相同的值),然后第一个给i
赋予一个新的值(比如从1到6),另一个线程执行同样的操作(从1到6) 。
同步必须从读取到分配一个值。 你的第一次同步没有任何效果(读int
是primefaces),第二次同步。 在我看来,这是正确的forms:
void synchronized incIBy5() { i += 5 } void incIBy5() { synchronized(this) { i += 5 } } void incIBy5() { synchronized(this) { int temp = i; i = temp + 5; } }
将variables声明为volatile意味着修改其值会立即影响variables的实际内存。 编译器不能优化对variables的任何引用。 这保证了当一个线程修改variables时,所有其他线程立即看到新的值。 (这不能保证非易失性variables。)
声明一个primefacesvariables可以保证对variables的操作是以primefaces方式进行的,也就是说,操作的所有子步骤都是在它们被执行的线程内完成的,而不会被其他线程中断。 例如,增量和testing操作需要将variables递增,然后与另一个值进行比较; 一个primefaces操作保证这两个步骤将被完成,就像它们是一个不可分割/不可中断的操作一样。
将所有访问同步到一个variables允许一次只有一个线程访问该variables,并强制所有其他线程等待该访问线程释放其对variables的访问。
同步访问类似于primefaces访问,但是primefaces操作通常在较低级别的编程中实现。 另外,完全可以将一些访问同步到一个variables,并允许其他访问不同步(例如,将所有的写入同步到一个variables,但是没有读取同步)。
primefaces性,同步性和波动性是独立的属性,但通常组合使用来强制正确的线程协作访问variables。
附录 (2016年4月)
同步访问variables通常使用监视器或信号量来实现。 这些是低级的互斥 (互斥)机制,它允许线程独占地获得对variables或代码块的控制,如果所有其他线程也尝试获取相同的互斥量,则强制所有其他线程等待。 一旦拥有线程释放互斥量,另一个线程可以轮stream获取互斥量。
附录 (2016年7月)
同步发生在一个对象上 。 这意味着调用类的同步方法将locking调用的this
对象。 静态同步方法将lockingClass
对象本身。
同样,input一个同步块需要locking该方法的this
对象。
这意味着如果同步的方法(或块)同时locking在不同的对象上,则可以同时在多个线程中执行,但是对于任何给定的单个对象,只有一个线程可以同时执行同步的方法(或块)。
挥发性:
volatile
是一个关键字。 volatile
强制所有线程从主内存而不是caching中获取variables的最新值。 访问volatilevariables不需要locking。 所有线程可以同时访问volatilevariables值。
使用volatile
variables可以降低内存一致性错误的风险,因为任何对volatilevariables的写操作都会build立一个before-before关系,并且随后读取同一个variables。
这意味着对其他线程总是可见的对volatile
variables的更改 。 更重要的是,这也意味着当一个线程读取一个volatile
variables时,它不仅会看到volatile
的最新变化,还会看到导致变化的代码的副作用 。
何时使用:一个线程修改数据,其他线程必须读取最新的数据值。 其他线程将采取一些行动,但他们不会更新数据 。
AtomicXXX:
AtomicXXX
类支持单variables的无锁线程安全编程。 这些AtomicXXX
类(如AtomicInteger
)解决了在multithreading中访问的volatilevariables修改的内存不一致性错误/副作用。
何时使用:多个线程可以读取和修改数据。
同步:
synchronized
是关键字,用于保护方法或代码块。 通过使方法同步有两个作用:
-
首先,同一个对象上的两个
synchronized
方法的调用是不可能交错的。 当一个线程正在执行一个对象的synchronized
方法时,所有其他调用同一对象的synchronized
方法的线程将阻塞(暂停执行),直到第一个线程完成对象。 -
其次,当一个
synchronized
方法退出时,它会自动build立一个与先前synchronized
对象的任何后续调用synchronized
方法的before-before关系。 这保证了对所有线程都可见的对象状态的改变。
何时使用:多个线程可以读取和修改数据。 您的业务逻辑不仅更新数据,而且还执行primefaces操作
即使实现不同, AtomicXXX
也相当于volatile + synchronized
。 AmtomicXXX
扩展了volatile
variables+ 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
我知道两个线程不能同时进入Synchronize块
两个线程不能在同一个对象上input同步块两次。 这意味着两个线程可以在不同的对象上input相同的块。 这种混淆可能导致这样的代码。
private Integer i = 0; synchronized(i) { i++; }
这不会像预期的那样行事,因为它每次都可以locking在不同的对象上。
如果这是真的比这个atomic.incrementAndGet()如何工作没有同步? 并且是线程安全的?
是。 它不使用locking来实现线程安全。
如果你想知道他们如何更详细地工作,你可以阅读他们的代码。
内部读写与易失variables/primefacesvariables之间有什么区别?
primefaces类使用易变的字段。 这个领域没有什么区别。 不同的是所执行的操作。 Atomic类使用CompareAndSwap或CAS操作。
我读了一些文章,线程有variables的本地副本是什么?
我只能假定它指的是每个CPU都有自己的内存caching视图,这个caching视图可以与其他CPU不同。 为确保您的CPU具有一致的数据视图,您需要使用线程安全技术。
当共享内存至less有一个线程更新它时,这只是一个问题。
易失性+同步是一种操作(语句)完全primefaces化的傻瓜certificate解决scheme,其中包括对CPU的多条指令。
说例如:volatile int i = 2; 我++,这是什么,但我=我+ 1; 这使得我在执行这个语句后在内存中的值为3。 这包括从存储器中读取i(即2)的现有值,加载到CPU累加器寄存器中,并通过将现有值加1(累加器中的2 + 1 = 3)进行计算,然后写回该增加的值回到内存。 虽然价值是我是挥发性的,这些操作是不够primefaces的。 我是易变的保证只有一个单一的读/写从内存是primefaces,而不是多个。 因此,我们需要在i ++的周围同步,以保持它是一个愚蠢的primefaces语句。 记住一个陈述包含多个陈述的事实。
希望解释清楚。
Java volatile修饰符是保证线程之间进行通信的特殊机制的一个例子。 当一个线程写入一个volatilevariables,另一个线程看到写入时,第一个线程告诉第二个线程关于所有内存的内容,直到写入该volatilevariables。
primefaces操作在一个单位的任务中执行,不受其他操作的干扰。 primefaces操作在multithreading环境中是必要的,以避免数据不一致。