请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

双重检查模式中volatile起到的作用

老师在本节中10:55秒处的解释,说假如没有volatile修饰,线程2如果看不到线程1运行这句:instance = new Singleton6();,线程2就会走入if语句:

if(instance == null) { 
	instance = new Singleton6();
}

但是其实线程2是可以看到线程1运行这句的:instance = new Singleton6();,理由如下:

synchronized可以保证happens-before。当线程1在synchronized代码块中,线程2是被卡在synchronized代码块之外的,只有当线程1运行完synchronized代码块并释放monitor,线程2才能进入synchronized代码块,而当线程2进入到synchronized代码块中的时候,线程1之前在synchronized代码块中的所有修改已经对线程2可见了。

所以我认为这里的volatile关键字并不是为了保证可见性。只是单纯的为了禁止重排序。

不知道这样分析对不对,希望老师指教,谢谢!
参考资料:

// 双重检查的方法实现Singleton
public class Singleton6 {

    private volatile static Singleton6;

    private Singleton6(){ }

    public static  Singleton6 getInstance() {
        if(instance == null) {
            synchronized (Singleton6.class) {
                if(instance == null) {
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }


}

正在回答

4回答

在本小节的后面也对于synchronized保证第二个线程一定可以看到第一个不为null的happens-before情况作了说明,这是synchronized的happens-before所保证的。

1 回复 有任何疑惑可以回复我~
  • 提问者 慕勒4339842 #1
    非常感谢!
    回复 有任何疑惑可以回复我~ 2019-09-13 00:45:24
  • 老师,两个问题
    问题背景描述:
    老师的示例中用的是类锁,而非对象锁,这样的话,所有的线程就都应该排队进入同步代码块,而前面视频13-6中"升华:对synchronized可见性的正确理解"提到,synchronized不仅保证了原子性,还保证了可见性,那么我就有点糊涂了
    1. 即便 new 一个对象实际上分成3步,可能存在指令重排的话,那synchronized的原子性的特点,就不能保证synchronized中 new 操作的原子性吗?
    如果不能保证,是不是与synchronized原子性操作的这个结论有矛盾;
    如果可以保证原子性,那volatile是不是就没有必要再加了?
    2. 该楼主同学的参考链接中(http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)的双重检测锁代码中,synchronized用的是对象锁this,这个时候
    如果调用该方法的不同线程引用的是同一个实例,那么问题就回归到第一问;
    如果调用该方法的不同线程引用的不是同一个实例,那么这个时候synchronized(this)其实也就不起作用了,这个时候真正起作用的恰恰是需要volatile来防止指令重排序了,但是在synchronized(this)不起作用的前提下,加volatile与否是不是就没有意义了?
    回复 有任何疑惑可以回复我~ 2019-09-18 23:40:10
  • 1:synchronized可以保证原子性,但是原子性和重排序是并存的,重排序了不代表就失去了原子性。原子性是一系列操作全部成功或者不成功,但是不保证这系列操作的内部顺序。
    2:是同一个实例,回到第一问。
    回复 有任何疑惑可以回复我~ 2019-09-19 00:14:52
weixin_慕仙6522334 2020-05-23 13:28:46

老师是否可以这样理解:在第一个和第二个线程进入第一重检查时,第一个线程先进入synchronize代码块时如出现重排序的情况下,会出现对象非空,但是内容为空的情况。这个时候如果CPU切走了,又有第三第四个线程访问getInstance方法,这个时候第三第四线程就会检查到实例不为空(但是内容实际上还没有构造),然后使用的话就会报NPE错误。最后第一个线程切回来执行完剩下的内容构造方法,保证了原子性和可见性。然后第二个线程再进入synchronize代码块执行也符合逻辑。

4 回复 有任何疑惑可以回复我~
  • 悟空 #1
    恩对的
    回复 有任何疑惑可以回复我~ 2020-05-23 13:40:37
  • Barea #2
    厉害 看了半天还是你总结得最好
    回复 有任何疑惑可以回复我~ 2020-06-30 13:28:27
  • 明白了,可以这么理解:线程1通过了第一个条件判断,抢到锁,通过了第二个条件判断,在生成instance的时候,发生了重排序,生成对象引用之后给对象属性赋值之前,线程2执行到了第一个条件判断,判断为true,直接返回,而此时对象属性还没有赋值,后续使用这个对象的时候有可能发生NPE,然后线程1接着执行,给对象属性赋值,返回instance。自此,往后的所有线程调用getInstance()方法时都不会产生NPE问题。所以,我们需要防止重排序,保证在生成对象引用之前已经执行完了构造方法,因此,给instance加上volatile修饰。
    回复 有任何疑惑可以回复我~ 2021-09-15 16:33:52
weixin_慕工程5399315 2019-11-10 19:00:42

我的理解是这样,是否正确?

synchronized可以保证原子性和可见性,但是不会禁止重排序,原子性和重排序并不冲突,synchronized同步块内的代码实际执行时还是允许重排序优化的。

实际上,synchronized和锁,是以多线程执行串行化的方式来保证多线程执行同步块代码的有序性的,并不保证同步块内的代码执行时不会重排序。

这里加volatile,其实就是为了禁止重排序的。

2 回复 有任何疑惑可以回复我~
  • 悟空 #1
    说得很对!
    回复 有任何疑惑可以回复我~ 2019-11-10 19:26:38
卜硕 2020-08-05 23:43:40

老师你看我这样理解对不对,就是当A线程调用getInstance方法时,实例为null,那么此时执行new操作,如果不加volatile的话,不能保证赋值的原子性和重排序性,导致此时B线程访问该方法时instance不为null,导致获取的实例不正确。加了volatile之后用了保证了赋值的原子性和禁止重排序,才使得B线程不会拿到一个不完整的实例

0 回复 有任何疑惑可以回复我~
  • 悟空 #1
    恩对的
    回复 有任何疑惑可以回复我~ 2020-08-06 10:09:07
  • synchronized可以保证原子性
    回复 有任何疑惑可以回复我~ 2021-09-15 16:40:04
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信