并发整理(一)— Java并发底层原理

现已全部整理完,其他两篇
并发整理(二)— Java线程与锁
并发整理(三)— 并发集合类与线程池

本篇主要是底层的东西。

Java内存模型/JMM

Java并发采用的是共享内存模型。线程的通信隐式进行,整个通信过程对程序员完全透明。所以要理解其中隐式的规则,否则会引起一些内存可见性问题。

java的堆内存是可以共享的,但是栈内存是私有的。

线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

  1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

从整体来看,这两个步骤实质上是线程 A 在向线程 B 发送消息,而且这个通信过程必须要经过主内存。JMM 通过控制主内存与每个线程的本地内存之间的交互,来为 java 程序员提供内存可见性保证。

image

JMM分析

指令重排序

Happens-Before

这个关键字是JSR-133内存模型中用来阐述操作的内存可见性的。是JMM的核心概念。程序员要基于这个规则提供内存可见性保证来编程。

这个关系并不是说着前一个操作要在后一个操作之前,只是说前一个操作对后一个可见。

具体规则

  • 单个线程的任意操作happens-Before后序操作
  • volatilehappens-Beforevolatile读,原因在后面
  • 对一个锁解锁happens-Before对一个锁加锁,原因也在后面
  • 线程start一定happens-Before线程中任意操作
  • 线程join成功之后一定happens-Before与返回操作
  • 满足传递性

这个关系就是让JMM来对编译器与处理器的重排序做约束

JMM保证

因为有编译器与处理器优化的存在,所以有重排序存在的必要性。

但是JMM保证,在正确同步的情况下不改变程序的执行结果,尽可能让编译器与处理器优化。

也可以说在满足程序员定义的happens-Before规则来执行的结果与优化的结果肯定一致。

有一点要注意,JMM不保证64位的long/double变量写的原子性,因为32位处理器要执行64位数据的指令需要拆分成两个单独执行。jdk5以前的JMM,64的读/写都是分开的,jdk5以后只有写会拆分。

数据依赖性

只要两个操作访问同一个变量,并且有一个写操作,那么就说这俩是数据依赖关系。

写-读写-写读-写都是。

但是只对单个线程的操作和单个处理器执行的指令有效。

重排序

编译器会对指令序列做优化,并不会按照我们写的顺序执行

  1. 编译器在不改变单线程中语义前提进行重排序
  2. 处理器可以改变不存在数据依赖性的语句重排序
  3. 由于读/写缓冲区,内存系统进行的重排序

1是编译器重排序,2、3属于处理器重排序。

JMM重排序规则对编译器是禁止特定类型的重排,对处理器而是采用内存屏障指令的方法。

不同的处理器都有不同的重排序规则,所以java有对应的4个内存屏障指令来禁止这些重排序。

  • StoreLoad最强大,就是强制让写缓冲全部刷新到内存然后再读取,大部分关键字都靠这个实现
  • 其他三个类似LoadLoadStoreStoreLoadStore

比如:

class Test{
  int a=0;
  boolean flag=false;
  
  public void writer(){
    a=1;//1
    flag=true;//2
  }
  
  public void reader(){
    if(flag) //3
      int i=a*a;//4
  }
}
//A线程先执行writer,B线程执行reader
//1和2不存在数据依赖,可以重排
//3和4不存在数据依赖,可以重排(处理器可以把指令拆分,让a*a提前读,3成立再赋值,所以3、4中的指令可以重排序)
//1和4在多线程中不考虑数据依赖,所以结果会不一样

并发原语

Volatile

会java的都知道volatile的特点

  • 可见性:只要修饰变量,就对所有线程可见,看到其最后写入
  • 原子性:任意单个volatile变量读/写都具有原子性

JMM怎么做到的

具体做法:
  • 每个volatile写操作前插入一个StoreStore屏障
  • 每个volatile写操作后插入一个StoreLoad屏障
  • 每个volatile读操作后插入一个LoadLoad屏障
  • 每个volatile读操作后插入一个LoadStore屏障

举例来说:StoreStore屏障的意义在于volatile写之前,所有普通写操作已经对任意处理器可见。保证这个屏障之前的写已经刷到主存。后面再加一个StoreLoad,就是防止与后面普通读重排序。volatile读类似。

效果

最终形成的可重排序效果:

第一个操作 第二个操作
是否能重排序 普通读/写 volatile读 volatile写
普通读/写 NO
volatile读 NO NO NO
volatile写 NO NO

然后再看上面的例子就理解了

class VolatileTest{
  int a=0;
  volatile boolean flag=false;
  
  public void writer(){
    a=1;//1
    flag=true;//2
  }
  
  public void reader(){
    if(flag) //3
      int i=a*a;//4
  }
}
//A线程先执行writer,B线程执行reader
//现在2 happens-before 3,又因为单线程中1 happens-before 2同理3、4
//根据传递性现在1 happens-before 4,保证结果单一

:我们说的volatile的原子性是指它单一的读和写,像++这样的复合操作不具有原子性

为什么volatile不能保证原子性而Atomic可以

那volatile怎么保证刷回主存

在解析volatile变量写的时候,会多出一个lock汇编指令,该指令在多核处理器下会

  • Lock前缀指令执行期间,以前的处理器会锁住总线来,但是开销有点大,所以现在处理器会锁处理部分的内存区域,用缓存一致性来阻止两个以上的处理器缓存修改内存区域
  • 写回结束后会被其他处理器嗅探到,然后其他处理器会把该部分置为无效,重新刷新

正是因为会锁住内存,所以有的时候在高速缓存行是64位的处理器中,我们可以将volatile变量最加到64位来提高其并发的效率。

关于如何更好使用可以看这个

正确使用 Volatile 变量

Final

final用于修饰常量代表不可变,也可以修饰方法和类

所以编译器和处理器处理的时候,要保证final的赋值规范

怎么做到的

  • final写之后插入一个StoreStore屏障
  • final读前面插入一个LoadLoad屏障

当然这些都是针对大部分处理器,不同情况也会不同。

效果

  • 对象的 final 域已经被正确初始化过了之后,才会对其他线程可见,final写也不会重排序到构造函数之外
  • 在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象的引用

极客并发教程-final语义

锁的内容非常多,下一篇单独整理。这里写概述。

显式锁Lock

ReentrantLock都是基于volatile关键字来实现的。

通过一个volatile变量Status来控制同步的状态,使那些没有获得的线程自旋或者阻塞来实现效果。

  • 公平锁获取的时候会读volatile,所以具有volatile语义
  • 非公平锁获取时会先读,然后用CAS来更新,所以同时具有volatile写和读的语义
  • 锁释放的时候都会写volatile写语义

隐式锁synchronized

synchronized之所以会叫隐式锁是因为编译器自动帮我们通过一个monitor的对象来完成。

Java中每个对象都可以作为锁,所以synchronized存在Java的对象头里。

对于synchronized代码块,JVM的实现是插入monitorenter和monitorexit指令来实现的。

方法的同步也可以用这种方式,但是JVM没有详细说明。

synchronized原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容