JMM理解( 二 )

  • 一个变量同一时间只能有一个线程对其进行 lock 操作 。 但 lock 操作可以被同一条线程重复执行多次 , 多次 lock 之后 , 必须执行相同次数 unlock 才可以解锁 。
  • 如果对一个变量进行 lock 操作 , 会清空所有工作内存中此变量的值 。 在执行引擎使用这个变量前 , 必须重新 load 或 assign 操作初始化变量的值 。
  • 如果一个变量没有被 lock , 就不能对其进行 unlock 操作 。 也不能 unlock 一个被其他线程锁住的变量 。
  • 一个线程对一个变量进行 unlock 操作之前 , 必须先把此变量同步回主内存 。
  • JMM 三大特征JMM 三大特征分别是:原子性 , 可见性 , 有序性 。 整个 JMM 实际上也是围绕着这三个特征建立起来的 , 并且也是 Java 并发编程的基础 。
    原子性
    原子性是指一个操作是不可分割、不可中断的 , 要么全部执行成功要么全部执行失败 。
    JMM 只能保证对基本数据类型的变量的读写操作是原子性的 , 但 long 和 double 除外(long 和 double 的非原子性协定) 。
    我们来看看下面的例子:
    int x = 1;
    int y = x;
    x ++;
    上面三行代码只有第一行是原子性操作 , 基本类型赋值操作 , 必定是原子性操作 。
    第二行代码先读取 x 变量的值 , 再进行赋值给 y 变量 , 进行了两个操作 , 不能保证原子性 。
    第三行代码先读取 x 变量的值 , 再进行加 1 , 最后再赋值给 x 变量 , 进行了三个操作 , 不能保证原子性 。
    在并发环境下 , 为了保证原子性 , Java 提供了 synchronized 关键字 。 因此在 synchronized 修饰的代码块之间的操作都是原子性的 。
    【JMM理解】可见性
    可见性是指所有线程都能看到共享内存的最新状态 。 即当一个线程修改了一个共享变量的值时 , 其他线程能够立即看到该变量的最新值 。
    对于可见性问题 , Java 是提供了一个 volatile 关键字来保证可见性 。 当一个共享变量被 volatile 关键字修饰时 , 这个变量被修改后会立即刷新到主内存 , 保证其他线程看到的值一定是最新的 。
    除了 volatile 关键字之外 , final 和 synchronized 也能实现可见性 。
    final 关键字修饰的变量 , 在构造器中一旦初始化完成 , 如果没有对象逸出(指对象没有初始化完成就可以被别的线程使用) , 那么其他线程都就可以看见 final 修饰的变量 。
    synchronized 的原理是 , 线程进入 synchronized 代码块后 , 线程会获取到 lock , 将会清空本地内存 , 然后从主内存中拷贝共享变量的最新值到本地内存作为副本 , 执行代码 , 又将修改后的副本值刷新到主内存中 , 最后线程执行 unlock 。
    有序性
    有序性是指程序执行的顺序按照代码的先后顺序执行 。
    在 Java 中 , 可以通过 volatile 和 synchronized 关键字来保证多线程之间操作的有序性 。
    volatile 关键字是通过在主存中加入内存屏障来达到禁止指令重排序 , 来保证有序性 。
    synchronized 关键字原理是 , 一个变量在同一时刻只能被一个线程 lock , 并且必须 unlock 后 , 其他线程才可以重新 lock , 使得被 synchronized 修饰的代码块在多线程之间是串行执行的 。