前言
后面两期咱们介绍了多线程的根底知识点,都是一些面试高频问题,没有看和遗记的小伙伴能够回顾一下。
《蹲坑也能进大厂》多线程这几道根底面试题,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面试和各类知识点,有趣味的小伙伴欢送关注,一起学习,一起哈。
原创不易,你怎忍心白嫖,如果你感觉这篇文章对你有点用的话,感激老铁为本文点个赞、评论或转发一下,因为这将是我输入更多优质文章的能源,感激!