【并发编程】(学习笔记-共享模型之内存)-part4

public void println(int x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

3.有序性


3-1 诡异的结果

看下面一个栗子:

int num = 0;

boolean ready = false;

// 线程1 执行此方法

public void actor1(I_Result r) {

if(ready) {

r.r1 = num + num;

} else {

r.r1 = 1;

}

}

// 线程2 执行此方法

public void actor2(I_Result r) {

num = 2;

ready = true;

}

看到这里可能聪明的小伙伴会想到有下面三种情况:

情况1:线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1

情况2:线程2 先执行 num = 2,但没来得及执行 ready = true,线程1 执行,还是进入 else 分支,结果为1

情况3:线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4(因为 num 已经执行过了)

但其实还有可能为0哦! 😲

有可能还是:线程 2 执行 ready=true ,切换到线程1 ,进入if分支,相加为0,在切回线程 2 执行 num=2

这种现象就是指令重排

3-2 解决方法

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

@JCStressTest

@Outcome(id = {“1”, “4”}, expect = Expect.ACCEPTABLE, desc = “ok”)

@Outcome(id = “0”, expect = Expect.ACCEPTABLE_INTERESTING, desc = “!!!”)

@State

public class ConcurrencyTest {

int num = 0;

volatile boolean ready = false;//可以禁用指令重排

@Actor

public void actor1(I_Result r) {

if(ready) {

r.r1 = num + num;

} else {

r.r1 = 1;

}

}

@Actor

public void actor2(I_Result r) {

num = 2;

ready = true;

}

}

3-3 有序性理解

同一线程内,JVM会在不影响正确性的前提下,可以调整语句的执行顺序,看看下面的代码:

static int i;

static int j;

// 在某个线程内执行如下赋值操作

i = …; // 较为耗时的操作

j = …;

可以看到,至于是先执行 i 还是 先执行 j ,对最终的结果不会产生影响。所以,上面代码真正执行时, 既可以是

i = …; // 较为耗时的操作

j = …;

也可以是

j = …;

i = …; // 较为耗时的操作

这种特性称之为指令重排多线程下指令重排会影响正确性

3-4 happens-before

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

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

static int x;

static Object m = new Object();

new Thread(()->{

synchronized(m) {

x = 10;

}

},“t1”).start();

new Thread(()->{

synchronized(m) {

System.out.println(x);

}

},“t2”).start()

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

volatile static int x;

new Thread(()->{

x = 10;

},“t1”).start();

new Thread(()->{

System.out.println(x);

},“t2”).start();

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

static int x;

x = 10;

new Thread(()->{

System.out.println(x);

},“t2”).start();

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

static int x;

Thread t1 = new Thread(()->{

x = 10;

},“t1”);

t1.start();

t1.join();

System.out.println(x);

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

static int x;

public static void main(String[] args) {

Thread t2 = new Thread(()->{

while(true) {

if(Thread.currentThread().isInterrupted()) {

System.out.println(x);//10

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);//10

}

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

  • 具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z

volatile static int x;

static int y;

new Thread(()->{

y = 10;

x = 20;//写屏障,y也会同步到主存

},“t1”).start();

new Thread(()->{

// x=20 对 t2 可见, 同时 y=10 也对 t2 可见

System.out.println(x);

},“t2”).start();

以上变量都是指共享变量即成员变量或静态资源变量

4.volatile 原理


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

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

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

4-1 如何保证可见性

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

public void actor2(I_Result r) {

num = 2;

ready = true; // ready 是 volatile 赋值带写屏障

// 写屏障

}

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

public void actor1(I_Result r) {

// 读屏障

// ready 是 volatile 读取值带读屏障

if(ready) {

r.r1 = num + num;

} else {

r.r1 = 1;

}

}

img

4-2 如何保证有序性

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

public void actor2(I_Result r) {

num = 2;

ready = true; // ready 是 volatile 赋值带写屏障

// 写屏障

}

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

public void actor1(I_Result r) {

// 读屏障

// ready 是 volatile 读取值带读屏障

if(ready) {

r.r1 = num + num;

} else {

r.r1 = 1;

}

}

img

但是,不能解决指令交错

  • 写屏障仅仅是保证本线程之后的读能够读到最新的结果,但不能保证其他线程读跑到它前面去

  • 有序性的保证也只是保证了本线程内相关代码不被重排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tbO5BtPY-1636074951144)(C:\Users\30287\AppData\Roaming\Typora\typora-user-images\image-20211102193927299.png)]

4-3 double-checked locking 问题

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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

最新整理面试题
在这里插入图片描述

上述的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题

最新整理电子书

在这里插入图片描述

最新整理大厂面试文档

在这里插入图片描述

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

最新整理面试题
[外链图片转存中…(img-pmOemGIg-1713547959091)]

上述的面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2021收集的一些大厂的面试真题

最新整理电子书

[外链图片转存中…(img-H5yZJUFP-1713547959093)]

最新整理大厂面试文档

[外链图片转存中…(img-g0w2nFbw-1713547959094)]

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值