Java并发(三)---内存

一、Java 内存模型

JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、
CPU 指令优化等。

JMM 体现在以下几个方面

  • 原子性 - 保证指令不会受到线程上下文切换的影响
  • 可见性 - 保证指令不会受 cpu 缓存的影响
  • 有序性 - 保证指令不会受 cpu 指令并行优化的影响

二、原子性

Java并发(二)—Monitor

三、可见性

1.volatile

易变关键字:它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。

注意1:volatile能保证的可见性,但并不保证原子性

注意2: synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低

例如:
volatile int i=0;

线程一:i++;
线程二:i--volatile只能保证他们的得到的i是最新值,但不能避免字节码指令的交错。

2.CPU缓存结构

在这里插入图片描述
在这里插入图片描述

四、有序性

JVM 会在不影响正确性的前提下,可以调整语句的执行顺序。
这种特性称之为『指令重排』多线程下『指令重排』会影响正确性

1.指令级并行

为什么要有重排指令这项优化呢?从 CPU执行指令的原理来理解。

现代处理器会设计为一个时钟周期完成一条执行时间最长的 CPU 指令。为什么这么做呢?可以想到指令
还可以再划分成一个个更小的阶段,例如,每条指令都可以分为:取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 这 5 个阶段。

instruction fetch (IF)
instruction decode (ID)
execute (EX)
memory access (MEM)
register write back (WB)

在这里插入图片描述
在不改变程序结果的前提下,这些指令的各个阶段可以通过重排序和组合来实现指令级并行来提高效率

现代 CPU 支持多级指令流水线,例如支持同时执行 取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 的处理器,就可以称之为五级指令流水线。这时 CPU 可以在一个时钟周期内,同时运行五条指令的不同阶段(相当于一条执行时间最长的复杂指令),IPC = 1,本质上,流水线技术并不能缩短单条指令的执行时间,但它变相地提高了指令地吞吐率。
在这里插入图片描述
可以看出,不同指令的小操作之间的执行顺序发生了变化,但提高了效率。

2.内存屏障

Memory Barrier(Memory Fence)

可见性:

  • 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
  • 读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据

有序性

  • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
  • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

3.volatile原理

volatile 修饰的变量,可以禁用指令重排

  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障

不能解决指令交错

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读(写屏障之后的代码)重排序到它前面去
  • 有序性的保证只是保证了本线程内相关代码不被重排序,不能保证多线程间的指令交错

4.double-checked locking

final class Singleton {
    private Singleton() { }
    private static volatile Singleton INSTANCE = null;
    public static Singleton getInstance() {
        // 实例没创建,才会进入内部的 synchronized代码块
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                // 也许有其它线程已经创建实例,所以再判断一次
                //避免多个线程都进入if后,重复创建,所以再加一个if判断
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

特点:

  • 懒惰实例化
  • 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁,降低了多次加锁的性能消耗

为什么使用volatile修饰INSTANCE
注意:第一个 if 使用了 INSTANCE 变量,是在同步块之外。虽然synchronized保证了原子性、可见性、有序性,但对于INSTANCE,由于它出现在了同步块外,并不能保证它的有序性。

同步块内:

//其中四条字节码指令:
new 表示创建对象,将对象引用入栈 // new Singleton
dup 表示复制一份对象引用 // 引用地址
invokespecial 表示利用一个对象引用,调用构造方法
putstatic 表示利用一个对象引用,赋值给 static INSTANCE

如果发生指令重排,先执行putstatic,再执行invokespecial。

在多线程下:
线程一执行putstatic后,还未执行invokespecial,线程二执行getstatic(第一个if),得到的是一个非空引用,但这个引用的对象还未完成构造方法,会发生错误。

所以:对 INSTANCE 使用 volatile 修饰,禁用指令重排。

5.happens-before原则

happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛
开以下 happens-before 规则,JMM 并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见

JVM定义的Happens-Before原则是一组偏序关系:对于两个操作A和B,这两个操作可以在不同的线程中执行。如果A Happens-Before B,那么可以保证,当A操作执行完后,A操作的执行结果对B操作是可见的。

  1. 程序顺序规则
    一个线程内部,按照程序代码的书写顺序,书写在前面的代码操作Happens-Before书写在后面的代码操作。这是因为Java语言规范要求JVM在单个线程内部要维护类似严格串行的语义,如果多个操作之间有先后依赖关系,则不允许对这些操作进行重排序。

  2. 锁定规则
    对锁M解锁之前的所有操作Happens-Before对锁M加锁之后的所有操作。

static int x;
static Object m = new Object();

new Thread(()->{
    synchronized(m) {
        x = 10;
    }
},"t1").start();

//t1执行完成后,对x的写操作,t2是可见的
new Thread(()->{
    synchronized(m) {
        System.out.println(x);
    }
},"t2").start();
  1. volatile变量规则
    线程对 volatile 变量的写,对接下来其它线程对该变量的读可见

  2. 线程启动规则
    线程 start 前对变量的写,对该线程开始后对该变量的读可见

  3. 线程结束规则
    线程结束前对变量的写,对其它线程得知(比如其它线程调用 t1.isAlive() 或 t1.join()等待它结束)它结束后的读可见

  4. 中断规则
    线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过t2.interrupted 或 t2.isInterrupted)

  5. 终结器规则
    一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。
    对象调用finalize()方法时,对象初始化完成的任意操作,同步到全部主存同步到全部cache。

  6. 传递性规则
    如果操作A Happens-Before B,B Happens-Before C,那么可以得出操作A Happens-Before C。

//例如:
volatile static int x;
static int y;

//先执行t1
new Thread(()->{
	y = 10;
	//对x赋值操作
	x = 20;
	//添加写屏障,之前的操作都会同步到内存
},"t1").start();

//再执行t1
new Thread(()->{
	// x=20 对 t2 可见, 同时 y=10 也对 t2 可见
	System.out.println(x);
},"t2").start();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值