Android技术知识Android进阶之路Android开发

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

2017-05-16  本文已影响336人  kachidokima

现已全部整理完,其他两篇
并发整理(二)— 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的核心概念。程序员要基于这个规则提供内存可见性保证来编程。

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

具体规则

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

JMM保证

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

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

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

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

数据依赖性

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

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

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

重排序

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

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

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

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

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

比如:

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的特点

JMM怎么做到的

具体做法:

举例来说: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汇编指令,该指令在多核处理器下会

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

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

正确使用 Volatile 变量

Final

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

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

怎么做到的

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

效果

极客并发教程-final语义

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

显式锁Lock

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

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

隐式锁synchronized

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

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

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

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

synchronized原理

上一篇 下一篇

猜你喜欢

热点阅读