(十) 共享模型之内存【有序性】
创始人
2024-03-17 04:26:13
JVM 会在不影响正确性的前提下,可以调整语句的执行顺序 这种特性称之为『指令重排』,多线程下『指令重排』会影响正确性。为什么要有重排指令这项优化呢?从 CPU 执行指令的原理来理解一下吧

一、原理之指令级并行(了解)(P141)

1. 鱼罐头的故事

  

2. 名词

(1)Clock Cycle Time 主频的概念大家接触的比较多,而 CPU 的 Clock Cycle Time(时钟周期时间),等于主频的倒数,意思是 CPU 能够识别的最小时间单位,比如说 4G 主频的 CPU 的 Clock Cycle Time 就是 0.25 ns,作为对比,我们墙上挂钟的 Cycle Time 是 1s 例如,运行一条加法指令一般需要一个时钟周期时间
(2)CPI 有的指令需要更多的时钟周期时间,所以引出了 CPI (Cycles Per Instruction)指令平均时钟周期数
(3)IPC IPC(Instruction Per Clock Cycle) 即 CPI 的倒数,表示每个时钟周期能够运行的指令数
CPU 执行时间 程序的 CPU 执行时间,即我们前面提到的 user + system 时间,可以用下面的公式来表示
程序 CPU 执行时间 = 指令数 * CPI * Clock Cycle Time

3. 指令重排序优化

在不改变程序结果的前提下,这些指令的各个阶段可以通过重排序组合来实现指令级并行,这一技术在 80's 中叶到 90's 中叶占据了计算架构的重要地位。

 指令重排的前提是,重排指令不能影响结果

二、诡异的结果

public class ConcurrencyTest {int num = 0;boolean ready = false;@Actorpublic void actor1(I_Result r) {if(ready) {r.r1 = num + num;} else {r.r1 = 1;}}@Actorpublic void actor2(I_Result r) {num = 2;ready = true;}}

 

三、解决方法

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

四、原理之 volatile

volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)

(1)对 volatile 变量的写指令后会加入写屏障

(2)对 volatile 变量的读指令前会加入读屏障

1. 如何保证可见性

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据 

 

2. 如何保证有序性

写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
还是那句话,不能解决指令交错: (1)写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去 (2)而有序性的保证也只是保证了本线程内相关代码不被重排序

 

3. 以著名的 double-checked locking 单例模式为例

public final class Singleton {private Singleton() { }private static Singleton INSTANCE = null;public static Singleton getInstance() {if(INSTANCE == null) { // t2// 首次访问会同步,而之后的使用没有 synchronizedsynchronized(Singleton.class) {if (INSTANCE == null) { // t1INSTANCE = new Singleton();}}}return INSTANCE;}
}
很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外

INSTANCE 没完全受到 synchronized 的保护,所以 INSTANCE  可能会指令重排

4. double-checked locking 解决

对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效
读写 volatile 变量时会加入内存屏障(Memory Barrier(Memory Fence)),保证下面两点: (1)可见性 1️⃣写屏障(sfence)保证在该屏障之前的 t1 对共享变量的改动,都同步到主存当中 2️⃣而读屏障(lfence)保证在该屏障之后 t2 对共享变量的读取,加载的是主存中最新数据 (2)有序性 1️⃣写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后 2️⃣读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前 (3)更底层是读写变量时使用 lock 指令来多核 CPU 之间的可见性与有序性

 

五、happens-before

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

(1)线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见

public class Ces {static int x;static Object m = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (m) {x = 10;}}, "t1").start();new Thread(() -> {synchronized (m) {System.out.println(x);}}, "t2").start();}
}

(2)线程对 volatile 变量的写,对接下来其它线程对该变量的读可见

public class Ces {volatile static int x;public static void main(String[] args) {new Thread(()->{x = 10;},"t1").start();new Thread(()->{System.out.println(x);},"t2").start();}
}

(3)线程 start 前对变量的写,对该线程开始后对该变量的读可见

public class Ces {static int x;public static void main(String[] args) {x = 10;new Thread(()->{System.out.println(x);},"t2").start();}
}

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

public class Ces {static int x;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{x = 10;},"t1");t1.start();t1.join();System.out.println(x);}
}

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

public class Ces {static int x;public static void main(String[] args)  {Thread t2 = new Thread(()->{while(true) {if(Thread.currentThread().isInterrupted()) {System.out.println(x);break;}}},"t2");t2.start();new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}x = 10;t2.interrupt();},"t1").start();while(!t2.isInterrupted()) {Thread.yield();}System.out.println(x);}
}

(6)对变量默认值(0,false,null)的写,对其它线程对该变量的读可见

(7)具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z ,配合 volatile 的防指令重排,有下面的例子

public class Ces {volatile static int x;static int y;public static void main(String[] args)  {new Thread(()->{y = 10;x = 20;},"t1").start();new Thread(()->{// x=20 对 t2 可见, 同时 y=10 也对 t2 可见System.out.println(x);},"t2").start();}
}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...