据我所知,volatile
有助于内存可见性并synchronized
有助于实现执行控制.Volatile
只是保证线程读取的值将写入最新值.
考虑以下:
public class Singleton{ private static volatile Singleton INSTANCE = null; private Singleton(){} public static Singleton getInstance(){ if(INSTANCE==null){ synchronized(Integer.class){ if(INSTANCE==null){ INSTANCE = new Singleton(); } } } return INSTANCE; } }
在上面的代码中,我们使用双重检查锁定.这有助于我们只创建一个Singleton实例,这是communicated
通过创建线程尽快创建的其他线程.这就是关键字的volatile
作用.我们需要上面的内容,synchronized block
因为线程读取INSTANCE
变量的延迟为null并初始化对象可能会导致a race condition
.
现在考虑以下内容:
public class Singleton{ private static Singleton INSTANCE = null; private Singleton(){} public static synchronized Singleton getInstance(){ if(INSTANCE==null){ INSTANCE = new Singleton(); } return INSTANCE; } }
假设我们有2个线程t1
并t2
试图获取该Singleton
对象.线程首先t1
进入getInstance()
方法并创建INSTANCE
对象.现在,这个新创建的对象应该对所有其他线程可见.如果INSTANCE
变量不是volatile
那么我们如何确保对象仍然不在t1's
内存中并且对其他线程可见.如何尽快在上述INSTANCE
被初始化t1
可见的其他线程?
这是否意味着始终使变量易变为同步?在什么情况下我们不要求变量是易变的?
PS我已经在StackOverflow上阅读了其他问题,但找不到我的问题的答案.请在投票前发表评论.
我的问题来自这里给出的解释
我认为你所缺少的是JLS 17.4.4:
监视器m上的解锁动作与m上的所有后续锁定动作同步(其中"后续"根据同步顺序定义).
这与关于volatile变量的内容非常相似:
对易失性变量v(第8.3.1.4节)的写入与任何线程对v的所有后续读取同步(其中"后续"根据同步顺序定义).
然后在17.4.5:
如果动作x与后续动作y同步,那么我们也有hb(x,y).
......其中hb是"发生在之前"的关系.
然后:
如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并在第二个之前被命
内存模型非常复杂,我并不认为自己是专家,但我的理解是引用部分的含义是你所展示的第二种模式是安全的,而变量不是易变的 - 实际上任何变量都是是仅修改和同步块内的读出对于相同的显示器是不是挥发性安全.对我来说,更有趣的方面是变量值引用的对象内的变量会发生什么.如果Singleton
不是不可变的,你仍然可能在那里遇到问题 - 但这一步就被删除了.
更具体地说,如果两个线程调用getInstance()
时INSTANCE
为null,则其中一个线程将首先锁定监视器.INSTANCE
在解锁操作之前发生非空引用的写操作,并且在另一个线程的锁定操作之前发生解锁操作.锁定操作发生在读取INSTANCE
变量之前,因此写入发生在读取之前...此时,我们保证写入对读取线程可见.