我同意John的评论之一:在访问非最终变量时,必须始终使用最终锁伪变量,以防止在变量的引用更改的情况下出现不一致。因此,在任何情况下,作为第一个经验法则:
规则1:如果字段为非最终字段,请始终使用(私有)最终锁定虚拟对象。
原因1:您持有锁并自己更改了变量的引用。在同步锁之外等待的另一个线程将能够进入受保护的块。
原因2:您持有锁,而另一个线程更改了变量的引用。结果是相同的:另一个线程可以进入受保护的块。
但是,当使用最终锁定虚拟对象时,还有另一个问题:您可能会得到错误的数据,因为非最终对象仅在调用sync(ize)对象时才与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){
}
}
}
}
如您所见,我将两个锁直接写在同一行上,因为它们始终属于同一组。这样,您甚至可以做10个嵌套锁:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
请注意,如果您仅像synchronized (LOCK3)
其他线程一样获得内部锁,则此代码不会中断。但是,如果您在另一个线程中调用如下代码,它将导致中断:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
在处理非最终字段时,只有一种解决此类嵌套锁的方法:
规则2-替代:将对象的所有字段声明为volatile。(我不会在这里谈论这样做的缺点,例如,即使对于读取,也禁止在x级缓存中进行任何存储,例如aso。)
因此,aioobe是完全正确的:只需使用java.util.concurrent。或者开始了解有关同步的一切,并使用嵌套锁自己完成。;)
有关为什么非最终字段上的同步中断的更多详细信息,请查看我的测试用例:https : //stackoverflow.com/a/21460055/2012947
有关更多详细信息,为什么由于RAM和缓存而需要完全同步的原因,请在此处查看:https : //stackoverflow.com/a/21409975/2012947
o
到达同步块时所引用的对象的独占访问权。如果o
引用的对象发生更改,则可以引入另一个线程并执行同步的代码块。