JMM、Volatile(Juc-10)

本贴最后更新于 1845 天前,其中的信息可能已经东海扬尘

概述

谈谈你对 volatile 的理解

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

什么是 JMM

java 内存模型,是一个不存在的东西,是一个概念,一个约定!

关于 JMM 一些同步的约定:

  1. 线程解锁前一定把共享变量立刻刷回主储存
  2. 线程加锁钱必须把主存最新值 copy 到工作内存中
  3. 加锁一定是同一把锁

线程,工作内存,主内存一共有 4 组 8 种操作

image.png

内存交互操作

  内存交互操作有 8 种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于 double 和 long 类型的变量来说,load、store、read 和 write 操作在某些平台上允许例外)

    • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
    • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
    • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用
    • load (载入):作用于工作内存的变量,它把 read 操作从主存中变量放入工作内存中
    • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
    • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
    • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的 write 使用
    • write  (写入):作用于主内存中的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中

  JMM 对这八种指令的使用,制定了如下规则:

    • 不允许 read 和 load、store 和 write 操作之一单独出现。即使用了 read 必须 load,使用了 store 必须 write
    • 不允许线程丢弃他最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有 assign 的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施 use、store 操作之前,必须经过 assign 和 load 操作
    • 一个变量同一时间只有一个线程能对其进行 lock。多次 lock 后,必须执行相同次数的 unlock 才能解锁
    • 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新 load 或 assign 操作初始化变量的值
    • 如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其他线程锁住的变量
    • 对一个变量进行 unlock 操作之前,必须把此变量同步回主内存

  JMM 对这八种操作规则和对 volatile 的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用 java 的 happen-before 规则来进行分析。

问题

程序不知道主内存的值已经被修改过了,类似这样的代码就会出现死循环

package net.yscxy.tvolatile;

import java.util.concurrent.TimeUnit;

/**
 * @Author WangFuKun
 * @create 2020/12/2 20:21
 */
public class JmmTest {
    static  int number = 0;

    public static void main(String[] args) throws InterruptedException {//main线程
        new Thread(() -> { //线程1对主内存的变化是不可见的,不知道的
            while (number == 0) {
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        number = 1;
        System.out.println(number);
    }

}

volatile

  1. 保证可见性
package net.yscxy.tvolatile;

import java.util.concurrent.TimeUnit;

/**
 * @Author WangFuKun
 * @create 2020/12/2 20:21
 */
public class JmmTest {
    static volatile int number = 0;

    public static void main(String[] args) throws InterruptedException {//main线程
        new Thread(() -> { //线程1对主内存的变化是不可见的,不知道的
            while (number == 0) {
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        number = 1;
        System.out.println(number);
    }

}
  1. 不保证原子性

原子性:不可分割

也就是线程 A 在执行任务的时候是不能被打扰和被分割的,要么同时成功,要么同时失败

package net.yscxy.tvolatile;

/**
 * @Author WangFuKun
 * @create 2020/12/2 21:39
 */
//测试不保证原子性
public class VDemo02 {
    private volatile static int number = 0;

    public  static void add() {
        number++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        //因为默认有jc线程和主线程这两个线程一直活跃着
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(number);
    }
}

如果不加 lock 或者 synchronized 如何保证原子性

image.png

方案:使用原子类,解决原子性问题 AtomicInteger

AtomicInteger 他的底层用的是 CAS

package net.yscxy.tvolatile;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author WangFuKun
 * @create 2020/12/2 21:39
 */
//测试不保证原子性
public class VDemo02 {
    //使用原子类进行解决
    private volatile static AtomicInteger number = new AtomicInteger();

    public synchronized static void add() {
        //number++
        //当然这里并不简单的是一个加一操作
        number.getAndIncrement(); //AtomicInteger +1方法,CAS
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        //因为默认有jc线程和主线程这两个线程一直活跃着
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(number);
    }
}

这些类的低层直接和操作系统挂钩,在内存中修改值,Unsafe 是一个很特殊的存在

  • JUC
    17 引用 • 3 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...