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

  • 相关阅读:
    今天开始用 VSU 2010
    Visual Studio 2010 模型设计工具 基本应用
    Asp.Net访问Oracle 数据库 执行SQL语句和调用存储过程
    Enterprise Library 4.1 Security Block 快速使用图文笔记
    解决“System.Data.OracleClient 需要 Oracle 客户端软件 8.1.7 或更高版本。”(图)
    一个Oracle存储过程示例
    Enterprise Library 4.1 Application Settings 快速使用图文笔记
    Oracle 10g for Windows 简体中文版的安装过程
    Oracle 11g for Windows 简体中文版的安装过程
    Oracle 9i 数据库 创建数据库 Net 配置 创建表 SQL查询 创建存储过程 (图)
  • 原文地址:https://www.cnblogs.com/hupu-jr/p/8397911.html
Copyright © 2011-2022 走看看