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一段时间,这期间让另一个线程去读取被改变的值,会发现其实是可以读到的。

 

posted @ 2019-01-23 16:53  facelessvoidwang  阅读(746)  评论(0编辑  收藏  举报