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一段时间,这期间让另外一个线程去读取被改变的值,会发现实际上是能够读到的。
相关文章
相关标签/搜索