共计 4018 个字符,预计需要花费 11 分钟才能阅读完成。
前言
后面两期咱们介绍了多线程的根底知识点,都是一些面试高频问题,没有看和遗记的小伙伴能够回顾一下。
《蹲坑也能进大厂》多线程这几道根底面试题,80% 小伙伴第一题就答错
《蹲坑也能进大厂》多线程系列 - 上下文、死锁、高频面试题
本章次要是剖析一下大家十分 面生 的 Java 内存模型,用代码的形式介绍重排序、可见性以及线程之间通信等原理,大家看完本篇必然有更加分明的意识和了解。
狗剩子:花 GieGie~, 节日快乐啊!这么早就来蹲坑。
我:哟,狗剩子你明天又来加班了,365 天无休啊你。
狗剩子:这不明天过节,没有什么好货色送给各位看官,只能肝进去一些干货送给老铁们么。
我:接招吧,狗儿。
注释
我:书接上文,狗剩子你给大伙讲讲什么是 volatile?
上来就搞这么刺激的吗,你让咱家想想 …
我:ok,小辣鸡,那我换个问题,你理解过 Java 内存模型吗?
这个不是三伏天喝冰水,正中下怀么。
Java 内存模型(Java Memory Model)简称 JMM,首先要晓得它是一组标准,是一组 多线程拜访 Java 内存 的标准。
咱们都晓得市面上 Java 虚拟机品种有很多,比方 HotSpot VM、J9 VM 以及各种实现(Oracle / Sun JDK、OpenJDK),而每一种虚拟机在解释 Java 代码、并进行重排序时都有本人的一套流程,如果没有 JMM 标准,那很有可能雷同代码在不同 JVM 解释后,失去的运行后果也是不统一的,这是咱们不心愿看到的。
我:有点意思,但这种说法还是有点含糊,你再具体说说它都有哪些标准?
厌恶,就晓得你会这么问,小伙们提到 Java 内存模型咱们第一工夫要想到 3 个局部,重排序
、 可见性
、 原子性
。
-
重排序
先看一段代码,给你几分钟工夫,看看这段代码输入有几种后果
private static int x = 0, y = 0; private static int a = 0, b = 0; Thread one = new Thread(new Runnable() { @Override public void run() { a = 1; x = b; } }); Thread two = new Thread(new Runnable() { @Override public void run() { b = 1; y = a; } }); two.start(); one.start(); one.join(); two.join(); System.out.println("x ="+x+", y ="+y);
你的答案是不是这三种呢
如果是的话,那么祝贺你,能够持续和狗哥我一块持续往下钻研 第四种状况
这里我减少了一个 for 循环,能够循环打印,直到打印本人想要的后果,小伙伴们本人运行一下。
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for (; ;) {
i++;
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch latch = new CountDownLatch(3);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {latch.countDown();
latch.await();} catch (InterruptedException e) {e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {latch.countDown();
latch.await();} catch (InterruptedException e) {e.printStackTrace();
}
b = 1;
y = a;
}
});
thread2.start();
thread1.start();
latch.countDown();
thread1.join();
thread2.join();
String result = "第" + i + "次(" + x + "," + y + ")";
if (x == 0 && y == 0) {System.out.println(result);
break;
} else {System.out.println(result);
}
}
}
看看你执行到多少次会呈现呢,这里我是执行到将近 17 万次。
为什么会呈现这种状况呢,那是因为这里产生了 重排序,在重排序后,代码的执行程序变成了:
- y=2;
- a=1;
- x=b;
- b=1;
这里就能够总结一下 重排序
,艰深的说就是代码的执行程序和代码在文件中的 程序不统一 ,代码指令并没有严格依照代码语句程序执行,而是依据本人的规定进行调整了,这就是 重排序
。
我:这个例子有点货色,简单明了,我都看懂了?那可见性又怎么了解呢
既然例子比拟直观,那这个问题我持续用例子来解释一波。
- 可见性
public class Visibility {
int a = 1;
int b = 2;
private void change() {
a = 3;
b = a;
}
private void print() {System.out.println("b=" + b + ";a=" + a);
}
public static void main(String[] args) {while (true) {Visibility visibility = new Visibility();
// 线程 1
new Thread(() -> {
try {Thread.sleep(1);
} catch (InterruptedException e) {e.printStackTrace();
}
visibility.change();}).start();
// 线程 2
new Thread(() -> {
try {Thread.sleep(1);
} catch (InterruptedException e) {e.printStackTrace();
}
visibility.print();}).start();}
}
}
这里同样倡议停留几分钟,你感觉 print()打印后果有几种呢,多思考能力了解更粗浅。
- a=1,b=2 : 线程 1 未执行到
change()
,此时线程 2 已执行print()
- a=3,b=2: 线程 1 执行到
change()
的 a = 3,而后线程 2 正好执行print()
- a=3,b=3:线程 1 执行完
change()
,而后线程 2 执行print()
这是大家最容易想到和了解的 ( 如果没有想到,记得去补习一下花 Gie 的前两篇根底),然而还有一种状况比拟非凡:
- b=3,a=1
是不是没想到啊 (手动得意),这里咱们如果线程 1 执行完change()
办法后,此时 a= 3 且 b =3
,然而这时只是 线程 1
本人晓得这个后果值,对于 线程 2
来说,他可能只看到了一部分,呈现这种状况的起因,是因为线程之间通信是有延时的,而且多个线程之间不会进行实时同步,所以 线程 2
只看到了 b 的最新值,并没有看到 a 的扭转。
我:你这么说的话,我如同有点明确了,但还不是很清晰。
你能够再说说这个变量是怎么传递的吗,为什么线程 2 没有接管到 a 的变动呢?
好的呢,我都依你,我间接上个简略的草图吧。
<image width=300 src=”https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/69b6f4691db84a31805c0ee0016a45d9~tplv-k3u1fbpfcp-watermark.image”></image>
<image width=300 src=”https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c254ade514ce44c583b66ecf36e8c4c6~tplv-k3u1fbpfcp-watermark.image”></image>
图中咱们剖析出以下 4 个步骤。
- 每个线程都会从主内存中获取变量,保留在本人的工作内存(线程公有)中,图 1 是
线程 1
、线程 2
初始化状态; - 图 2 是线程 1 执行完
change()
办法后,先将b=3
写回主内存(此时a=3
还尚未写回主内存) - 线程 2 从主内存获取最新数据
a = 1,b = 3
,并写到本人的工作线程 - 线程 2 最终打印出
a=1,b=3
我:这下子我都看明确了,那你给我总结一下为什么会呈现可见性起因吧,万一面试官问我我也好答复。
。。。
造成可见性的起因,次要是因为CPU 有多级缓存
,而每个线程会将本人须要的数据读取到独占缓存中,在数据批改后也是写入到缓存中,而后期待刷回主内存,这就导致了有些线程读写的值是一个过期的值。
我:有点 6,我给你先点个赞,那还要一个原子性呢?
原子性我再前面再进行介绍,因为咱们先理解 volatile
、synchronized
之后再理解会更简略(你认为我不会 volatile 么,斜眼笑)。明天就先到这里吧,写了这么多,大家都懒得看了。
总结
JMM 这块只是是十分重要的,熟练掌握当前在排查问题、写需要会更加得心应手,本篇原本想再多介绍一些其余内容,然而再写下去篇幅过长,成果就不是很好,所以先介绍这些,这里花 Gie 也强烈建议小伙伴们能亲手敲一下,纸上得来终觉浅,入手敲一敲当前写代码才不会虚。
下一章花 Gie 会持续介绍 happens-before
、volatile
、 内存构造进阶
等,心愿大家继续关注,今天假期完结了,咱们持续肝。
点关注,防走丢
以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花 Gie,有问题大家随时留言探讨,咱们下期见🦮。
文章继续更新,能够微信搜一搜「Java 开发零到壹」第一工夫浏览,后续会继续更新 Java 面试和各类知识点,有趣味的小伙伴欢送关注,一起学习,一起哈🐮🥃。
原创不易,你怎忍心白嫖,如果你感觉这篇文章对你有点用的话,感激老铁为本文 点个赞、评论或转发一下,因为这将是我输入更多优质文章的能源,感激!