zoukankan      html  css  js  c++  java
  • synchronized的可见性理解

      之前的时候看《并发编程的艺术》,书中提到dcl写法的单例模式是有问题的,有可能会导致调用者得到一个创建了一半的对象,从而导致报错。修复办法是将单例对象的引用添加volatile进行修饰,禁用重排序,则外界获取的就一定是已经创建好的对象了。
      光说总是不行的,上代码:
    public class SingleTest {
        private static SingleTest singleTest;   // 这个应该用volatile修饰
        //获取单例的方法
        public static SingleTest getInstance() {
            if(singleTest == null){
                synchronized (SingleTest.class){
                    if(singleTest == null){
                        singleTest = new SingleTest();
                    }
                }
            }
            return singleTest;
        }
    }

      对于这一段的分析说的很清楚,网上也有大量的文章,但我有一个疑问:不是说synchronized有原子性、可见性么,而且可见性是通过monitor exit的时候强制刷新内容到主内存来实现的,既然这样,那synchornized结束前,没有刷新到内存,外面的程序应该读不到这个单例对象的值才对啊,为什么会读到呢?这个synchronized 的可见性究竟该怎么理解?
      先说理解的错误之处:synchronized的可见性是通过monitor exit来保证的,这点没错,但monitor exit之前就不会刷新到主内存么,显然不是。现在jvm的机制,已经尽量快速的将改变同步到缓存了,这个机制是怎么确定的不清楚,但简单测试会发现非常短。
      另外,synchronized 的可见性的正确理解是:对于被synchronized修饰的代码块,如果A线程执行结束,会强制刷新线程缓存内容到内存,同时通知其它synchronized修饰的线程x的值无效,需要重新读取(这点跟volatile很相似),因此B线程在执行的时候也就能读到A线程对x的修改了,这就是synchronized的可见性。

      试一下如下示例:
    //可见性验证
    @Test
    public void testA() throws InterruptedException {
        //启动线程在不停监视str变化
        Thread th1 = new Thread(() -> {
            while(true){
                if(str.equals("b")){
                    System.out.println("th1 ==> str 已经被改为 b ," + Thread.currentThread());
                }
            }
        });
        Thread th2 = new Thread(() -> {
            while(true){
                synchronized (str){
                    if(str.equals("b")){
                        System.out.println("th2 ==> str 已经被改为 b ," + Thread.currentThread());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        th1.start();
        th2.start();
    
        //让监视线程都启动完毕
        Thread.sleep(3000);
    
        System.out.println("修改str的值为b");
        synchronized (str){
            str = "b";
        }
    
        Thread.sleep(3000);
    }
      执行结果:

      可以看到th1并没有输出,因为它线程中的str换出内容一致是“a”。实际上,33-35行可以不用synchronized,也会有相同结果,因为现在的jvm会尽最快速度将改变同步到缓存,而synchronized在执行的时候会重新读取,因此也会发现str的值被改变了,而th1则没有重新读取的机制,也就无法进行输出了。
      对于monitor exit之前也会刷新到内存这点,也可以通过程序进行验证,可以在synchronized中修改某个值,然后sleep一段时间,这期间让另一个线程去读取被改变的值,会发现其实是可以读到的。

  • 相关阅读:
    AGC007题解
    博弈论学习笔记
    ZROI2019 提高十连测
    男人八题2019
    LOJ 2840「JOISC 2018 Day 4」糖
    CF671D Roads in Yusland
    网络流套路小结
    BZOJ 3729 GTY的游戏
    AGC036C GP 2
    BZOJ 5046 分糖果游戏
  • 原文地址:https://www.cnblogs.com/nevermorewang/p/10310001.html
Copyright © 2011-2022 走看看