非最终字段的同步

每次我在非最终类字段上同步时,都会显示警告。 这里是代码:

public class X { private Object o; public void setO(Object o) { this.o = o; } public void x() { synchronized (o) // synchronization on a non-final field { } } } 

所以我改变了以下的编码方式..

  public class X { private final Object o; public X() { o = new Object(); } public void x() { synchronized (o) { } } } 

我不确定上面的代码是在非final类字段上进行同步的正确方法。 我怎样才能同步非最终字段?

首先,我鼓励你真正努力处理更高级别抽象的并发问题,即使用来自java.util.concurrent的类来解决它,例如ExecutorServices,Callables,Futures等。

这就是说,在非最终字段本身上同步没有任何问题 。 您只需要记住, 如果对象引用改变,同一段代码可能会并行运行 。 也就是说,如果一个线程运行同步块中的代码,并且有人调用setO(...) ,另一个线程可以同时在同 一个实例上运行相同的同步块。

同步你需要独占访问的对象(或者更好的是,一个对象“守护”它)。

这真的不是一个好主意 – 因为你的同步块不再以一致的方式进行真正的同步。

假设同步块意味着确保一次只有一个线程访问某些共享数据,请考虑:

  • 线程1进入同步块。 Yay – 它拥有对共享数据的独占访问权
  • 线程2调用setO()
  • 线程3(或仍然2 …)进入同步块。 伊克! 它认为它拥有对共享数据的独占访问权限,但是线程1仍然在与它交互…

你为什么这样的事情发生? 也许有一些非常特殊的情况下,它是有道理的…但是,在我感到高兴之前,你必须给我一个特定的用例(以及减轻上面给出的情况的方法)它。

我同意John的评论之一:在访问非最终variables时,必须始终使用最终locking虚拟variables,以防止variables引用更改时出现不一致。 所以在任何情况下,作为第一条经验法则:

规则1: 如果一个字段是非最终的,总是使用一个(私有的)最终锁假人。

原因#1:您持有锁,并自己更改variables的引用。 另一个在同步锁之外等待的线程将能够进入被保护的块。

原因2:您持有锁,另一个线程更改variables的引用。 结果是一样的:另一个线程可以进入防护块。

但是当使用最后一个locking虚拟对象时,还有另一个问题 :你可能会得到错误的数据,因为当调用synchronized(object)时,你的非最终对象只会与RAM同步。 所以,作为第二个经验法则:

规则2: 当locking一个非最终对象时,你总是需要执行以下两个操作:为了RAM同步,使用最后一个locking对象和非最终对象的locking。 (唯一的select是将对象的所有字段声明为volatile!)

这些锁也被称为“嵌套锁”。 请注意,您必须始终按照相同的顺序呼叫它们,否则您将会遇到死锁

 public class X { private final LOCK; private Object o; public void setO(Object o){ this.o = o; } public void x() { synchronized (LOCK) { synchronized(o){ //do something with o... } } } } 

正如你所看到的,我把两个锁直接写在同一行上,因为它们总是在一起。 像这样,你甚至可以做10个嵌套锁:

 synchronized (LOCK1) { synchronized (LOCK2) { synchronized (LOCK3) { synchronized (LOCK4) { //entering the locked space } } } } 

请注意,如果您只是通过其他线程获取像synchronized (LOCK3)这样的内部锁,则此代码不会中断。 但是如果你在另外一个线程中调用,就会中断:

 synchronized (LOCK4) { synchronized (LOCK1) { //dead lock! synchronized (LOCK3) { synchronized (LOCK2) { //will never enter here... } } } } 

在处理非最终字段时,这种嵌套锁只有一个解决方法:

规则2 – 备选: 声明对象的所有字段为volatile。 (我不会在这里讨论这样做的缺点,例如,即使读取也不允许在x级caching中存储任何内容)。

所以,aioobe是相当正确的:只要使用java.util.concurrent。 或者开始理解有关同步的所有内容,并使用嵌套locking自行完成。 ;)

有关非终结字段同步的更多细节,请查看我的testing用例: https : //stackoverflow.com/a/21460055/2012947

更多的细节为什么你需要同步所有由于内存和caching看看这里: https : //stackoverflow.com/a/21409975/2012947

如果oX的实例的生命周期中永不改变,则第二个版本是更好的样式,而不pipe是否涉及同步。

现在,第一个版本是否有什么问题是不可能的,如果不知道这个课程还在发生什么事的话。 我倾向于同意编译器,它看起来很容易出错(我不会重复其他人说的)。

只需加上我的两分钱:当我使用通过devise器实例化的组件时,出现了这个警告,所以它的字段不能是最终的,因为构造函数不能带参数。 换句话说,我没有最终的关键字,我有准决赛场。

我想这就是为什么只是警告:你可能做错了,但也可能是对的。

我在这里看不到正确的答案,也就是说, 完全可以这样做。

我甚至不知道为什么这是一个警告,它没有任何错误。 JVM确保您在读取值时返回(或为空) 某个有效的对象,并且可以在任何对象上进行同步。

如果你计划在使用锁的时候实际改变锁(而不是像在init方法中改变它,在你开始使用它之前),你必须使你打算改变volatile的variables。 然后,只需要在旧对象和新对象上进行同步,就可以安全地更改该值

 public volatile Object lock; 

 synchronized (lock) { synchronized (newObject) { lock = newObject; } } 

那里。 这并不复杂,使用锁(互斥锁)编写代码在事务上很简单。 编写没有它们的代码(locking免费代码)是困难的。

编辑:所以这个解决scheme(由Jon Skeetbuild议)可能有一个与“同步(对象){}”实现的primefaces性问题“,而对象引用正在改变。 我单独问,根据埃里克森先生它不是线程安全的 – 看到: 是进入同步块primefaces? 。 所以把它作为例子如何不做 – 与链接为什么;)

如果synchronized()是primefaces的,请参阅代码如何工作:

 public class Main { static class Config{ char a='0'; char b='0'; public void log(){ synchronized(this){ System.out.println(""+a+","+b); } } } static Config cfg = new Config(); static class Doer extends Thread { char id; Doer(char id) { this.id = id; } public void mySleep(long ms){ try{Thread.sleep(ms);}catch(Exception ex){ex.printStackTrace();} } public void run() { System.out.println("Doer "+id+" beg"); if(id == 'X'){ synchronized (cfg){ cfg.a=id; mySleep(1000); // do not forget to put synchronize(cfg) over setting new cfg - otherwise following will happend // here it would be modifying different cfg (cos Y will change it). // Another problem would be that new cfg would be in parallel modified by Z cos synchronized is applied on new object cfg.b=id; } } if(id == 'Y'){ mySleep(333); synchronized(cfg) // comment this and you will see inconsistency in log - if you keep it I think all is ok { cfg = new Config(); // introduce new configuration // be aware - don't expect here to be synchronized on new cfg! // Z might already get a lock } } if(id == 'Z'){ mySleep(666); synchronized (cfg){ cfg.a=id; mySleep(100); cfg.b=id; } } System.out.println("Doer "+id+" end"); cfg.log(); } } public static void main(String[] args) throws InterruptedException { Doer X = new Doer('X'); Doer Y = new Doer('Y'); Doer Z = new Doer('Z'); X.start(); Y.start(); Z.start(); } } 

AtomicReference适合您的要求。

从关于primefaces包的java文档:

一个支持单variables无锁线程安全编程的小型工具包。 本质上,这个包中的类将volatilevariables值,字段和数组元素的概念扩展为那些也提供了表单的primefaces条件更新操作的variables:

 boolean compareAndSet(expectedValue, updateValue); 

示例代码:

 String initialReference = "value 1"; AtomicReference<String> someRef = new AtomicReference<String>(initialReference); String newReference = "value 2"; boolean exchanged = someRef.compareAndSet(initialReference, newReference); System.out.println("exchanged: " + exchanged); 

在上面的例子中,你用你自己的ObjectreplaceString

相关的SE问题:

何时在Java中使用AtomicReference?