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

    一、背景

    最近在看<Java并发编程实战>这本书,看到共享变量的可见性,其中说到“加锁的含义不仅仅局限于互斥行为,还包括内存可见性”。

    我对于内存可见性第一反应是volatile:被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。

    原因是volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令,通过多处理器的缓存一致性协议,来保持变量的同步更新。

    但是我却没明白“加锁”与“可见性”这句话表达的意思,仔细思考下确实是这样子的。

    二、实践

     1 public class NoSivibilityVariable {
     2     private static boolean isOver = false;
     3     public static void main(String[] args) {
     4         Thread thread = new Thread(new Runnable() {
     5             @Override
     6             public void run() {
     7                     while (!isOver);
     8                     System.out.println("thread ----- true");
     9             }
    10         });
    11         thread.start();
    12         try {
    13             Thread.sleep(500);
    14         } catch (InterruptedException e) {
    15             e.printStackTrace();
    16         }
    17             isOver = true;
    18         System.out.println("main ----- true");
    19     }
    20 }

    执行上面这段代码,只会打印出“main ----- true”然后一直死循环阻塞。原因是当主线程把变量isOver修改为true,值的修改作用范围仅仅是当前线程内(主线程)而另外的线程是主内存的值,并没有读取主线程修改后的值,所以另一个线程和主内存的值都是失效的值。

    如果要解决这个情况怎么办?

    1.常见的做法就是把第二行 private static boolean isOver = false修改为 private static volatile boolean isOver = false就不会出现这种情况。

    2.就像书中所说的通过加锁来解决。

     1 package synchronized_word;
     2 
     3 public class NoSivibilityVariable {
     4     private static boolean isOver = false;
     5 
     6     public static void main(String[] args) {
     7         Thread thread = new Thread(new Runnable() {
     8             @Override
     9             public void run() {
    10                 synchronized (NoSivibilityVariable.class) {
    11                     while (!isOver);
    12                     System.out.println("thread ----- true");
    13                 }
    14             }
    15         });
    16         thread.start();
    17         
    18         
    19         try {
    20             Thread.sleep(500);
    21         } catch (InterruptedException e) {
    22             e.printStackTrace();
    23         }
    24         synchronized (NoSivibilityVariable.class) {
    25             isOver = true;
    26         }
    27         System.out.println("main ----- true");
    28     }
    29 }

    2个线程中在对共享变量的读取或者写入都进行加锁处理,因为线程对应的都是同一把锁对象(该类对象)所以相互会排斥。但是就算这样子也不能说明内存可见性的。其实真正解决这个问题的是JMM关于Synchronized的两条规定: 

    1、线程解锁前,必须把共享变量的最新值刷新到主内存中; 
    2、线程加锁时,讲清空工作内存中共享变量的值,从而使用共享变量是需要从主内存中重新读取最新的值(加锁与解锁需要统一把锁) 

    线程执行互斥锁代码的过程: 
    1.获得互斥锁 
    2.清空工作内存 
    3.从主内存拷贝最新变量副本到工作内存 
    4.执行代码块 
    5.将更改后的共享变量的值刷新到主内存中 
    6.释放互斥锁 

    http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#incorrectlySync

    这里提到synchronized会保证对进入同一个监视器的线程保证可见性。比如线程 t1修改了变量,退出监视器之前,会把修改变量值v1刷新的主内存当中;当线程t2进入这个监视器时,如果有某个处理器缓存了变量v1,首先缓存失效,然后必须重主内存重新加载变量值v1(这点和volatile很像)。这里语义的解读只是说了对于同一个监视器,变量的可见性有一定的方式可寻,非同一个监视器就不保证了

    三、总结

    synchronized具有内存可见性,为了确保所有线程能够看到共享变量的值是最新的,所有执行读操作或写操作的线程都必须在同一个锁上同步。

  • 相关阅读:
    【设计模式】6.模板方法模式
    【设计模式】5.原型模式
    【设计模式】4.工厂模式
    【设计模式】3.代理模式
    zookeeper集群的搭建
    zookeeper实现分布式锁的原理和一个小例子
    zookeeper配置管理实现原理----监听事件watch
    zookeeper的javaAPI操作(基于Curator的CRUD)
    java.lang.IllegalArgumentException: A HostProvider may not be empty!
    Zookeeper的安装和基本操作
  • 原文地址:https://www.cnblogs.com/hupu-jr/p/8397911.html
Copyright © 2011-2022 走看看