写文章

Java并发编程学习前期知识下篇

2020-03-20 09:54:39

444 | 0 | 0

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并发编程学习中!


编辑

0

收藏
分享