程序员:不能逃避的synchronize和volatile

本博客 猫叔的博客,转载请申明出处

阅读本文约 “10分钟”java

适读人群:Java 初级git

学习笔记,我也是呆呆作了很久,学了一下PS,而后继续思考了一会,再开始写出来的,但愿能够简明易懂。github

原子性

首先是咱们彼此都要保持一致的观点:原子(Atomic)操做指相应的操做是单一不可分割的操做缓存

emmmm,这里很牵强的解释下原子性,仍是不懂就搜搜其余文章,最好看看一些具体的例子多线程

首先是代码例子架构

对int型变量conut执行counter++的操做不是原子操做学习

这能够分为3个操做优化

  • 一、读取变量counter的当前值
  • 二、拿counter当前值和1作加法运算
  • 三、将counter的当前值增长1后赋值给counter变量

上面的步骤2,颇有可能在执行的时候就已经被其余线程修改了,其所为的“当前值”已是过时的spa

或者看看百度百科的例子线程

咱们以decl (递减指令)为例,这是一个典型的"读-改-写"过程,涉及两次内存访问。设想在不一样CPU运行的两个进程都在递减某个计数值,可能发生的状况是:

  • ⒈ CPU A(CPU A上所运行的进程,如下同)从内存单元把当前计数值⑵装载进它的寄存器中;
  • ⒉ CPU B从内存单元把当前计数值⑵装载进它的寄存器中。
  • ⒊ CPU A在它的寄存器中将计数值递减为1;
  • ⒋ CPU B在它的寄存器中将计数值递减为1;
  • ⒌ CPU A把修改后的计数值⑴写回内存单元。
  • ⒍ CPU B把修改后的计数值⑴写回内存单元。

内存里的计数值应该是0,然而它倒是1。两个进程都去掉了对该共享资源的引用,但没有一个进程可以释放它--两个进程都推断出:计数值是1,共享资源仍然在被使用

我再举例我呆想到的例子,一个姐姐和一个妹妹一块儿包饺子

image

画的很通常,别看我这样,我也是学过2小时速成素描的·····

假设咱们在一个黑盒环境下,就是两姐妹都在各自小空间包饺子,而后她们把饺子经过各自的小洞口放入一个大盒子里。她们并不知道对方(好比她们两刚刚由于妈妈不给零花钱而生气了)

这个时候她们各自同时边赌气边包了一个饺子,同时放到盒子里,妈妈跑过来问老大,盒子里有多少个了?她只知道一个。再问问老二,她也是回答一个。这个生活例子可能提交特殊,不过偶尔生活中由于信息不对称而致使的预知结果与实际有误差也是常常发生的

因此他们脑海就是这个状况。其实盒子里已是2个饺子了

image

那么其实这个场景也像是JVM

image

synchronize

synchronize关键字能够实现操做的原子性,其本质是经过该关键字所包括的临界区的排他性保证在任何一个时刻只有一个线程可以执行临界区中的代码

也就是说,如今妈妈说只有听她的,两姐妹才能有零花钱,因此她叫两个闹脾气的小鬼都到厨房,并拿出了大盒子,让她们从新开始,不过要按照妈妈的要求来

image

妈妈先让姐姐包了5个,由于两姐妹都在厨房,不是各自在房间,因此此次妹妹都看在眼里,接着妈妈让妹妹包10个,妹妹显然是有点不乐意了(凭什么我姐才5个),不过她仍是老实作了,如今他们三人都知道盒子里有15个

这里就又牵出了synchronize的另外一个特色,保证内存的可见性

它保证了一个线程执行临界区中的代码时所修改的变量值对于稍有执行该临界区中的代码的线程来讲是可见的,这对于保证多线程的代码是很是重要的

官方的解释下:CPU执行代码,为了减小变量访问的消耗,会将值缓存到CPU缓存区,再次访问的时候,就是从缓存区去读取而不是主内存,这里的缓存区有点相似姐姐脑海/妹妹脑海。并且代码对缓存区的修改可能仅修改缓存区,没有被写回主内存。因为CPU都有本身的存储区,对于不一样CPU的存储区内容是不可见的。这也是所谓的内存可见性

volatile

一样这个兄弟也能够保证内存可见性

一个线程对于一个采用volatile修改的变量的值的更改对于其余访问该变量的值的线程老是可见的

若是说对比synchronize和volatile的内存锁,而后说volatile是轻量级锁,emmmm,很差不太恰当

volatile的内部锁并不能保证操做的原子性。

他在内存可见性的核心机制是:修改的值会被写入主内存,且其余CPU缓存区的值会所以失效(而后再更新一个最新值),保证其余线程访问volatile修饰的变量老是最新值。

固然他也有一个核心做用:禁止指令重排序(Re-order)

大家通常怎么写5的?

image

假如以上是咱们的规定与但愿

可能编译器和CPU为了提供指令的执行效率可能会进行指令重排序(优化)

image

若是你但愿它是按照规定来的话就加上volatile,虽然可能会致使编译器和CPU没法对一些指令作可能的优化,假设上面那样写对于计算机来讲算优化:)

用程序来写一个例子:

private SomeOne object = new SomeOne();

你先想一下,你以为的顺序,好了,我说说计算机可能的顺序

  • 一、分配一段用于存储SomeOne的内存空间
  • 二、对该内存空间引用赋值给变量object
  • 三、建立类SomeOne

若是当其余线程访问二、object变量的时候,仅获得一个指向存储SomeOne存储空间的引用,由于三、SomeOne还没建立

结语

但愿各位兄弟能看到一些新的风景,synchronize能够保证操做原子性,且保证内存可见性;volatile仅能保证内存可见性。

synchronize会致使上下文切换,volatile不会哦。

关于上下文切换的,能够去看公众号的上一篇文章

我是MySelf,还在坚持学习技术与产品经理相关的知识,但愿本文能给你带来新的知识点。

公众号:Java猫说

学习交流群:728698035

现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不按期干货。

Image Text

相关文章
相关标签/搜索