先谈谈硬件是如何工做的,举个例子,你在window操做系统上须要下载一个游戏(20M),就须要使用cpu和内存了,在这个过程当中cpu负责计算,好比计算下载进度,统计下载完成一共须要多少时间等,内存为cpu提供数据的,负责保存游戏的全部信息,好比游戏的大小(20M)数据。在这个过程当中,cpu从内存上取游戏大小这个数据,而后cpu去计算下载进度,把计算出的进度结果再写到内存,最终呈现到用户页面,大概对cpu和内存应该有个大概的认识了吧!看上去下载游戏这个过程分工明确,没有问题,但实际上cpu的计算速度比内存的存取速度高了不知道多少个数量级,这个过程cpu很空闲啊(如图一),cpu你闲着没事干那就是浪费资源浪费钱啊,这是个问题,因而人们就想了个办法,在内存上面加个(高速)缓存,若是是一些经常使用信息,好比游戏大小这个数据,那就不用在内存取了,直接在缓存上拿(如图二),而缓存设计的存取速度是很快的,固然价格也更高,若是恰好缓存上有这个游戏大小数据,这个操做在计算机的世界叫作缓存命中,这样就解决了cpu很闲的问题。哈哈,仍是举个简单例子吧,咱春节买票回家,尽管你的手速很快,可是仍是一票难求,12306官网响应速度慢,没办法家仍是要回的,那就找黄牛,虽然价格贵可是能解决你的痛点。这个例子中你,12306系统,黄牛分别对应cpu,内存和缓存,方便你理解。顺便说下,这个黄牛其实也是设计模式中的代理。(图三,图四)设计模式
了解了硬件架构,再来理解Java内存模型(JMM),如鱼得水,JMM是根据硬件架构映射出来的,不是真实存在的,硬件模型是物理的,是真实存在的,以下图所示,若是如今有两个线程AB须要同时将共享变量c的值加1,最终的程序运行的结果c的值多是3,也多是2。那咱们一块儿来看看程序执行过程吧,程序初始化,线程AB将拷贝主内存的共享变量c到各自的工做内存,此时工做内存A,工做内存B的初始化值c值都为1,初始化结束,以下图所示。这里能够把线程A理解成cpu1,线程B理解成cpu2,工做内存理解成高速缓存。这个过程由于工做内存是线程私有的,由于每一个高速缓存是属于不一样CPU是不可见的,工做内存A看不见工做内存B的c值为1,相反工做内存B也看不到工做内存A的c值。缓存
当线程AB同时将共享变量c加1时,若是线程A先获取时间片,此时工做内存A的c值加1等于2,而后由工做内存A将变量c=2同步到主内存,此时主内存c变量为2了,线程A执行结束,释放时间片给线程B,以下图所示。此时主内存会更新线程B的工做内存B,将c=2告诉线程B,并更新工做内存B的值c=2,此时B获取时间片,看到工做内存B值是c=2,加1,c=3,线程B将c=3写到主内存,此时主内存c的值就是3了,线程B执行结束,整个程序结束。其实在这个过程当中,还有一种意外状况,若是线程A执行结束后,将主内存的c值变为2,若是主内存c=2尚未同步更新到工做内存B呢?此时问题就来了,线程B获取时间片后发现本身的工做内存变量c仍是1,而后加1,此时c=2,将c再更新到主内存,此时主内存的值仍是2,主内存再同步c=2的值给线程B已经失去意义了,由于线程所有执行完毕。在这个程序执行过程当中,其实致使线程安全的本质问题是主内存通知不及时才致使发生的,这个案例中由于主内存不能及时将c=2的值更新到线程B的工做内存,致使线程B获取不到c已经更新为2了。安全
那问题来了,cpu各自带着私有缓存,线程带着各自私有工做内存,数据都靠着主内存来通讯,可是主内存恰恰又不给力啊,通知线程B的工做内存不给力,致使结果c=2或者c=3的,这就是出现了线程安全问题了,这种安全性问题是因为缓存不可见形成的,因而我开始怀恋单核cpu时代了,可是逃避也解决不了实际问题,因而那些聪明的人们就想既然缓存不一致,那全部缓存都实现统一的协议能够吗,下面咱们就简单聊下缓存一致性协议。多线程
1)总线Lock#锁。锁定总线的开销比较大,在缓存更新内存后,其余的cpu都会被锁定住,禁止与内存通讯,这样开销就大了。架构
2)MESI协议。这是缓存一致性协议的具体实现,它经过嗅探技术识别哪一个cpu想修改主内存缓存行信息,若是该缓存行是共享的,先将该缓存行刷新到主内存,再设置其余cpu的高速缓存的缓存行无效,但频繁的嗅探其余cpu想修改的共享数据,也会致使总线风暴。jvm
可见性,有序性,原子性是线程安全的三个重要指标。可见性对理解多线程很是很是很是重要!因为多核硬件架构的问题,cpu高速缓存之间自己是不可见的,必需要实现缓存一致性协议。咱们刚才上面也说了硬件方面的方案,多线程对共享变量是不可见的,Java方面也提供了两个关键字来保证多线程状况下共享变量的可见性方案。spa
在JVM手册中,当多线程写被volatile修饰的共享变量时,有两层语义。操作系统
1)该变量当即刷新到主内存。线程
2)使其余线程的共享变量当即失效。言外之意当其余线程须要的时候再从主内存取。设计
在上述案例中,若是c为一个布尔值而且被volatile修饰,那么当线程AB同时更新共享变量c时,此时c对于工做内存AB是可见的。
在JVM手册中,synchronized可见性也有两层语义。
1)在线程加锁时,必须从主内存获取值。
2)在线程解锁时,必须把共享变量刷新到主内存。
这两句说明了,时刻保持主内存数据最新,当新的线程获取锁须要从主内存获取值。
今天经过可见性的话题,引出了硬件架构,硬件架构由于多cpu高速缓存引出的不可见性问题,从而引出了解决可见性的方案,这是基于硬件的。从Java高级语言的角度,引出了基于硬件的映射模型JMM,并给出了jvm要实现可见性用的一些关键字。谢谢你们的观看,我是叫练,边叫边练。有疑问和错误欢迎留言和指正。