非最终字段的同步

div*_*ivz 81 java multithreading synchronized

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

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  
     {  
     }  
   }  
 } 
Run Code Online (Sandbox Code Playgroud)

所以我用以下方式改变了编码

 public class X  
 {  

   private final Object o;       
   public X()
   {  
     o = new Object();  
   }  

   public void x()  
   {  
     synchronized (o)
     {  
     }  
   }  
 }  
Run Code Online (Sandbox Code Playgroud)

我不确定上面的代码是在非final类字段上同步的正确方法.如何同步非最终字段?

aio*_*obe 117

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

话虽如此,在非最终字段本身同步是没有错的.您只需要记住,如果对象引用发生更改,则可以并行运行相同的代码段.即,如果一个线程在同步块中运行代码并且有人调用setO(...),则另一个线程可以同时在同一个实例上运行相同的同步块.

同步您需要独占访问的对象(或者更好的是,"保护"它的对象).

  • 我不同意你的经验法则 - 我更喜欢同步一个对象,其唯一目的是*守护*其他状态.如果你从来没有对锁定它的对象做任何事情,你肯定知道没有其他代码可以锁定它.如果锁定一个"真实"对象,然后调用其方法,那么该对象也可以自身同步,这使得更难以推断锁定. (39认同)
  • 正如我在回答中所说的那样,我认为我需要非常认真地对待它,为什么你会*希望*做这样的事情.我也不建议同步`this`,我建议在类*中创建一个最终变量,仅用于锁定*,这会阻止其他人锁定同一个对象. (9认同)
  • 我的意思是,*如果*您在非最终字段上进行同步,您应该知道这样一个事实,即代码片段以独占访问方式运行,对到达同步块时引用的对象“o”进行独占访问. 如果 o 所指的对象发生变化,另一个线程可以出现并执行同步代码块。 (2认同)

Jon*_*eet 43

这真的不是一个好主意 - 因为您的同步块不再以一致的方式真正同步.

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

  • 线程1进入同步块.是的 - 它拥有对共享数据的独占访问权限......
  • 线程2调用setO()
  • 线程3(或仍为2 ...)进入同步块.伊克!它认为它拥有对共享数据的独占访问权限,但是线程1仍在继续使用它...

你为什么这样呢?也许有一些非常特殊的情况,这是有道理的......但是在我满意之前你必须向我提供一个特定的用例(以及减轻我上面给出的那种情况的方法).它.

  • @VitBernatik:不会.如果线程X开始修改配置,线程Y会改变正在同步的变量的值,然后线程Z开始修改配置,那么X和Z都会同时修改配置,这很糟糕. (3认同)
  • @aioobe:但是然后线程1仍然可以运行一些代码,这些代码正在改变列表(并经常引用"o") - 并且*通过执行*部分方式开始改变不同的列表.这怎么会是一个好主意?我认为我们从根本上不同意是否以其他方式锁定您触摸的对象是个好主意.我宁愿能够在不知道其他代码在锁定方面做什么的情况下推断我的代码. (2认同)
  • @Felype:听起来你应该问一个更详细的问题作为一个单独的问题 - 但是,我经常创建单独的对象就像锁一样. (2认同)
  • @LinkTheProgrammer:"同步方法同步实例中的每个对象" - 不,它没有.这根本不是真的,你应该重新审视你对同步的理解. (2认同)

Mar*_*cus 12

我同意John的评论之一:在访问非final变量时必须始终使用最终锁定虚拟对象,以防止变量引用更改时出现不一致.所以在任何情况下,作为第一个经验法则:

规则#1:如果某个字段是非最终字段,请始终使用(私有)最终锁定虚拟字符.

原因#1:您持有锁并自行更改变量的引用.在同步锁外等待的另一个线程将能够进入受保护的块.

原因#2:您持有锁,另一个线程更改变量的引用.结果是一样的:另一个线程可以进入受保护的块.

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

规则#2:当锁定非最终对象时,您始终需要同时执行这两项操作:为了RAM同步,使用最终锁定虚拟对象和非最终对象的锁定.(唯一的替代方法是将对象的所有字段声明为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...
        }
        }  
    }  
} 
Run Code Online (Sandbox Code Playgroud)

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

synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
    //entering the locked space
}
}
}
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您只是synchronized (LOCK3)通过其他线程获取内部锁,则此代码不会中断.但如果你调用另一个类似这样的线程,它会破坏:

synchronized (LOCK4) {
synchronized (LOCK1) {  //dead lock!
synchronized (LOCK3) {
synchronized (LOCK2) {
    //will never enter here...
}
}
}
}
Run Code Online (Sandbox Code Playgroud)

处理非最终字段时,只有一种解决方法可以解决此类嵌套锁:

规则#2 - 备选:将对象的所有字段声明为volatile.(我不会在这里讨论这样做的缺点,例如,即使对于读取,也要防止在x级缓存中存储任何内容.)

所以aioobe是非常正确的:只需使用java.util.concurrent.或者开始了解有关同步的所有内容,并使用嵌套锁自行完成.;)

有关非最终字段上的同步中断的更多详细信息,请查看我的测试用例:https://stackoverflow.com/a/21460055/2012947

有关RAM和缓存需要同步的更多详细信息,请访问:https://stackoverflow.com/a/21409975/2012947