Java【 (并发)[编程学习】前期知识【下篇】

‘通过’上一篇[《Java【 (并发)[编程学习】前期知识上篇》我们(知)道了(在)Java (并发)[“《中》”{(“「的」”)可【见性】}是什么?volatile(“「的」”)定义以及JMM(“「的」”)定义。《我们先「来看」看几个大》厂「真实(“「的」”)面试题」:

「(编辑)」

「(编辑)」

「(编辑)」

《从上面几个真实(“「的」”)面试》问题「来看」,{我们可}以看到大厂(“「的」”)面 试都会问到 (并发)[相关[(“「的」”)问题。所以

Java (并发)[,这个无论是面试还是(在)工作“《中》”, (并发)[都是会遇到(“「的」”)。Java (并发)[包JUC(java.util.concurrent)有了解过哪些? (并发)[包实现最重要(“「的」”)是什么?其原理是什么(知)道吗?(何为)JMM{(“「的」”)可【见性】}?volatiile关键字是怎么实现变量可【见性】(“「的」”)?『如果』想要学好 (并发)[,『弄懂理解透』彻(“「的」”)话,《凯》哥觉得以下计算机(“「的」”) 知识还是[要了解了解。本次《Java (并发)[编程-前期准备知识》《凯》哥准备用两篇来介绍,主要包括以下内容:简单介绍〖 内存[〗之间可【见性】是什么?volatile关键字(在)Java语言规范“《中》”是怎么定义(“「的」”)?(知)道JVM但是你(知)道JMM{是什么吗}?《计算机“《中》”》CPU是怎么处理数据(“「的」”)?‘通过’CPU处理数据来深刻理解<线程>之间可【见性】。还有就是volatile是怎么保证可【见性】(“「的」”)呢?其实现(“「的」”)两条原理是什么?

CPU<相关知识>

先「来看」看《凯》哥电脑配置:

「(编辑)」

「(编辑)」

「(编辑)」

从上图,可以看到《凯》哥电脑

CPU处理是4 核[8<线程>,

缓存有三级。

【其“《中》”一级数据缓】存和指令缓存都是32K,

(二级缓存)256K,

三级缓存是6M.

(电脑(“「的」”)〖 内存[〗是)24G

〖为什么要说这些呢〗 ?

〖因为〗JVM运行程序(“「的」”)实体其实就是<线程>,而每个<线程>(在)创建(“「的」”)时【候】JVM【都会给其创】建一个工作〖 内存[〗(有些地方称之为:栈空间)。工作〖 内存[〗是每个<线程>自己(“「的」”)私有数据区域。Java〖 内存[〗模型“《中》”规定所有(“「的」”)变量都是存储(在)主〖 内存[〗“《中》”( 也就[是《凯》哥24G内〖 内存[〗“《中》”),主〖 内存[〗是共享〖 内存[〗区域,所有(“「的」”)<线程>都可以访问(“「的」”)( 也就[是说主〖 内存[〗“《中》”(“「的」”)数据,任意<线程>都可以访问)。但是<线程>对变量(“「的」”)操作,如读取,『修改』赋值操作是(在)从“《中》”〖 内存[〗“《中》”进行(“「的」”)。因此,一个<线程>要想操作一个变量,首先是要讲变量从主〖 内存[〗copy到自己(“「的」”)工作〖 内存[〗空间,(然后再对自己)工作空间“《中》”对变量操作,操作完成之后再将变量写回到主〖 内存[〗“《中》”去。<线程>是不能够直接操作主〖 内存[〗“《中》”(“「的」”)变量(“「的」”)。各个<线程>“《中》”(“「的」”)工作〖 内存[〗存储(“「的」”)其实就是主〖 内存[〗(“「的」”)一个变量副本拷贝。因此不同<线程>之间是无法访问到对方(“「的」”)工作〖 内存[〗(“「的」”)。<线程>间(“「的」”)通讯(值转递)必须‘通过’主〖 内存[〗来完成(“「的」”)。

『上面这么』大一段话,可以简单对应《凯》哥电脑配置:

<线程>:其实就是《凯》哥CPU(“「的」”)4 核[8<线程>“《中》”(“「的」”)<线程>

主〖 内存[〗:就是《凯》哥本子上(“「的」”)24G物理〖 内存[〗条

<线程>工作〖 内存[〗空间:就是缓存(「一二三级缓存区域」)

<线程>工作原理,【如下图】:

「(编辑)」

说明:

主〖 内存[〗“《中》”变量int i= 0;

cpu1“《中》”(“「的」”)<线程>1获取到i变量(“「的」”)时【候】,‘会’将i从主〖 内存[〗“《中》”copy一份到自己(“「的」”)工作区域, 也就[是cpu1 cache“《中》”,然后更新i(“「的」”)值为10;

cpu2“《中》”(“「的」”)<线程>2同样获取到i变量,从主〖 内存[〗“《中》”copy〖一份之后〗,(在)自己(“「的」”)工作区cpu2 cache“《中》”将i『修改』成了15;这种情况下就是多 核[多<线程>问题。

<线程>之间可【见性】深度理解

(在)这种情况下主〖 内存[〗“《中》”(“「的」”)i就是两个<线程>之间(“「的」”)共 享[变量了。那么怎么能确保cpu1(“「的」”)<线程>1『修改』i(“「的」”)值之后,通知cpu2“《中》”(“「的」”)<线程>2(“「的」”)工作区缓存无效呢?这个操作就是<线程>之间{(“「的」”)可【见性】}。

再举个现实生活“《中》”常用(“「的」”)例子:

比如,《凯》哥现(在)(在)和大家【分享】。“今天我发布”之后,你们大家(在)自己手机或者是PC网友上都能看到《凯》哥【分享】(“「的」”)知识点。这个时【候】有个网友A(在)看到《凯》哥【分享】(“「的」”)东西,感觉有点不好或者是举个其他(“「的」”)例子或者更容易理解。〖于〗是他把《凯》哥这个文章进行了『修改』。然后给《凯》哥留Y。告诉《凯》哥,《凯》哥看后,『觉得很不错』。【等明天】,《凯》哥发文章通知大家,(『如果』)用xxx(“「的」”)案例就跟容易让大家理解了。〖于〗是,你们大家(知)道,哦原来昨天(“「的」”)案例不是最新(“「的」”)了。放弃昨天(“「的」”),看看今天最新(“「的」”)案例。

『如果』上面案例看着是多<线程>那么可以这么分析:

主〖 内存[〗:《凯》哥

共享变量:《凯》哥【分享】(“「的」”)知识点

多个<线程>:各位看《凯》哥【分享】(“「的」”)网友

其“《中》”网友A『修改』了知识点(“「的」”)内容(网友A『修改』(“「的」”)是自己手机上(“「的」”)(工作区(“「的」”))知识点)后通知了《凯》哥,然后《凯》哥又通知了各位。各位(知)道原来自己手里(“「的」”)不是最新(“「的」”)了,《然后放弃重新获取最》新(“「的」”)。

这样来理解(“「的」”)话,就更容易理解<线程>{(“「的」”)可【见性】}

Volatitle是如何保证可【见性】(“「的」”)呢?

可以‘通过’JIT编译器生成(“「的」”)汇编指令来查看对volatile进行写操作时【候】,CPU都做了哪些事情?

‘如下代码’:

Volatile Singleton instance = new Singleton();

Instance是被volatitle修饰(“「的」”)。

【(在)使】用JIT编译器生成(“「的」”)汇编指令后,有一个重要(“「的」”)代码:

0x01a2de1d:xxxx:lock addl $0X0,(%esp);

我们可以看到,‘当一个’共享变量被volatile‘修饰之后’,(在)进行写操作(“「的」”)时【候】,“会多出一”些汇编代码Lock.(在)IA-32架构软件开发手册“《中》”,Lock前缀(“「的」”)指令当(在)多 核[处理器(“「的」”)时【候】会引发出两件事情:

1:将当前(“「的」”)处理器缓存行(“「的」”)数据写回(“「的」”)主〖 内存[〗“《中》”( 也就[是系统(“「的」”)物理缓存“《中》”);

2:同时这个写回〖 内存[〗(“「的」”)操作也会使其他CUP里缓存了〖 内存[〗地址(“「的」”)数据被置为无效。

Cpu处理数据方法:

【为了提高处】理数据,CPU不会直接从〖 内存[〗“《中》”获取数据操作(“「的」”)。

‘这里我们需要’电脑处理数据(“「的」”)速度排序:磁盘(硬盘)<〖 内存[〗<高速缓存<CPU。“我们可以看”出CPU(“「的」”)操作速度比〖 内存[〗快很多。所以,『如果』CPU直接操作〖 内存[〗,「不仅会影响处理」速度还有可能让〖 内存[〗使用寿命变短。这个时【候】,{高速缓存就解决这个问}题(“「的」”)。

所以,CPU(在)处理数据(“「的」”)时【候】会像将〖 内存[〗“《中》”(“「的」”)数据到期到高级缓存“《中》”(就是一二三级缓存),然后再缓存“《中》”进行操作(“「的」”)。

(在)多 核[处理器(“「的」”)时【候】,为了保证各个处理器之间缓存变量是一致(“「的」”),《就需要实现缓存一致性》‘协议’。「其操作就是」:各个CPU‘通过’嗅探(在)总线上传播(“「的」”)数据来实时检查自己缓存(“「的」”)值是不是已经过期了。『如果』发下自己缓存“《中》”(“「的」”)数据已经被『修改』了,则就‘会’将当前(“「的」”)处理器“《中》”缓存数据状态设置为无效,当这个处理器需要对这个数据进行操作(“「的」”)似乎和,会重新从主〖 内存[〗“《中》”,把最新(“「的」”)数据读取到自己缓存“《中》”。

Volatile 两条实现原[理

1:汇编代码(“「的」”)lock前缀指令会引起处理器缓存写回到主〖 内存[〗“《中》”。

当有lock指令(“「的」”)缓存,(在)其声言期间,能搞保证处理器可以独占任何共享(“「的」”)〖 内存[〗。同时缓存一致性会阻止同时『修改』由两个以上处理器缓存(“「的」”)〖 内存[〗区域数据

2:当一个处理器(“「的」”)缓存写回到主〖 内存[〗“《中》”之后,会导致其他处理器(“「的」”)缓存无效

这个是处理器见(“「的」”)控制‘协议’来维护内部缓存(“「的」”)

总结:

‘通过’这两篇《Java (并发)[编程前期准备知识》(“「的」”)了解,我们(知)道JMM,<线程>之间共享数据等知识,这样再接下来学习Java (并发)[编程就会简单一些了。接下来‘欢迎进入’Java (并发)[编程学习“《中》”!


「(编辑)」