Java做为平台无关性语言,JLS(Java语言规范)定义了一个统一的内存管理模型JMM(Java Memory Model)。JMM规定了jvm内存分为主内存和工做内存 ,主内存存放程序中全部的类实例、静态数据等变量,是多个线程共享的,而工做内存存放的是该线程从主内存中拷贝过来的变量以及访问方法所取得的局部变量,是每一个线程私有的其余线程不能访问。每一个线程对变量的操做都是以先从主内存将其拷贝到工做内存再对其进行操做的方式进行,多个线程之间不能直接互相传递数据通讯,只能经过共享变量来进行。程序员
从上图来看,线程1与线程2之间如要通讯的话,必需要经历下面2个步骤:面试
1. 首先,线程1把本地工做内存中更新过的共享变量刷新到主内存中去。数据库
2. 而后,线程2到主内存中去读取线程1以前已更新过的共享变量。多线程
典型的高并发引发的问题就存在因为线程读取到的数据尚未从另外的线程刷新到主内存中而引发的数据不一致问题。架构
JLS一共定义了8种操做来完成主内存与线程工做内存的数据交互:并发
1. lock:把主内存变量标识为一条线程独占,此时不容许其余线程对此变量进行读写jvm
2. unlock:解锁一个主内存变量分布式
3. read:把一个主内存变量值读入到线程的工做内存高并发
4. load:把read到变量值保存到线程工做内存中做为变量副本学习
5. use:线程执行期间,把工做内存中的变量值传给字节码执行引擎
6. assign:字节码执行引擎把运算结果传回工做内存,赋值给工做内存中的结果变量
7. store:把工做内存中的变量值传送到主内存
8. write:把store传送进来的变量值写入主内存的变量中
使用标准的操做再来重现一下上方的2个线程之间的交互流程则是这样的:
另外使用这8种操做也有一些规则:
1. 原子性:关于原子性的定义能够参考个人上篇文章《浅谈数据库事务》。在JLS中保证原子性的操做包括read、load、assign、use、store和write。基本数据类型(除了long 和double)操做都具备原子性。
若是须要更大范围的原子性操做的时候,可使用lock和unlock操做来完成这种需求。
2. 可见性:是指当一个线程修改了共享变量的值,其余线程是否可以当即得知这个修改。
由上方JMM的概念得知,线程操做数据是在工做内存的,当多个线程操做同一个数据的时候很容易读取到尚未被write到主内存变量的值。
Java是如何保证可见性的:volatile、synchronized、final关键字
3. 有序性:在并发时,程序的执行可能会出现乱序。给人的直观感受就是:写在前面的代码,会在后面执行。有序性问题的缘由是由于程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。关于指令重排会在下方讲。
int a=1; int b=2; int c=3; int d=4;
你能说出上方这段代码的执行顺序么?其实咱们可能理所固然的觉得它会从上往下顺序执行。事实上,在实际运行时,为了优化指令的执行顺序等,代码指令可能并非严格按照代码语句顺序执行的。上方的代码执行顺序可能彻底反过来,这个就是指令重排。
不过呢,指令重排也不是能够随意重排的,它须要遵照必定的规则:
1. 程序顺序规则:一个线程内保证语义的正确性。
2. 锁规则:解锁确定先于随后的加锁前。
3. volatile规则:对一个volatile的写,先于volatile的读。
4. 传递性:若是A 先于 B,且B 先于 C,那么A 确定先于 C。
5. start()规则:线程的start()操做先于线程的其余操做。
6. join()规则:线程的全部操做先于线程的关闭。
7. 程序中断规则:线程的中断先于被中断后执行的代码。
8. 对象finalize规则:一个对象的初始化完成先于finalize()方法。
volatile关键字旨在告诉虚拟机在这个地方要注意不能随意的进行指令重排,而虚拟机看到一个变量被volatile修饰之后就会采用一些特殊的手段来保证变量的可见性。不过要注意的是volatile关键字不能保证原子性。
针对于上面所涉及到的知识点我总结出了有1到5年开发经验的程序员在面试中涉及到的绝大部分架构面试题及答案作成了文档和架构视频资料免费分享给你们(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),但愿能帮助到您面试前的复习且找到一个好的工做,也节省你们在网上搜索资料的时间来学习,也能够关注我一下之后会有更多干货分享。