java虚拟机读书笔记 第十二章 java内存模型与线程

java内存模型

java虚拟机规范中试图定义一个java内存模型来屏蔽掉各类硬件和操做系统的内存访问差别,以实现让java程序在各个平台都能达到一致的内存访问效果。java

1.主内存和工做内存

java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。这些变量包括实例字段、静态字段和构成数组对象的元素,包括局部变量和方法操做,由于是线程私有的,不会被共享。程序员

java内存模型规定了全部的变量都存储在主内存上,此外每一个线程还有本身的工做内存,线程的工做内存中保存了被线程用到的变量的主内存副本拷贝,线程对变量的全部操做(读取和赋值)都必须在工做内存中进行,不能直接读写主内存中的变量。线程间变量的传值,须要经过主内存来完成。编程

2.内存间交互操做

主内存和工做内存的交互协议,即一个变量如何从主内存拷贝到工做内存、如何从工做内存同步会主内存的实现细节,java内存模型中定义了8中操做来完成,虚拟机必须保证每一种操做都是原子性的数组

  • lock(锁定):做用于主内存的变量,把一个变量标识为一个线程独占的状态;
  • unlock(解锁):做用于主内存的变量,把一个处于锁定状态的变量释放出来,能够被其余的线程访问;
  • read(读取):做用于主内存变量,把一个变量的值从主内存传输到线程的工做内存中,以便随后的load动做使用;
  • load(载入):做用于工做内存的变量,它把read操做从主内存获得的变量值放入工做内存的变量内存副本中;
  • use:做用于工做内存的变量,把工做内存的一个变量的值传递给执行引擎,每当虚拟机遇到一个须要使用到变量的值的字节码指令是都会执行这个操做;
  • assign(赋值):做用于工做内存的变量,把从执行引擎的接受到的执行结果赋值给工做内存的变量;
  • store(存储):做用于工做内存的变量,把工做内存一个变量的值传输到主内存中,以便随后write使用;
  • write(写入):做用于主内存的变量,把store操做从工做内存获得的值放入主内存的变量中。

此外java内存模型还规定了在执行8中操做的时候必须知足如下规则:安全

  1. 不容许read和load、store和write单独出现
  2. 不容许一个线程丢弃它的最近assign操做,即工做内存变量修改后必须同步回主内存;
  3. 不容许一个变量没有任何assign操做就把数据从工做内存同步回主内存;
  4. 一个新的变量只能在主内存中”生成“,不容许工做线程使用一个未被初始化的变量,即对一个变量进行use、store操做以前,必需要有load、assign操做;
  5. 一个变量在同一时刻只容许一个线程对其进行lock操做;
  6. 对一个变量lock时,会清空工做内存此变量的值,在使用时从新load或assign来初始化变量值;
  7. 若是一个变量没有lock操做,不容许对其执行unlock操做;
  8. 对一个变量执行unlock时必须执行store、write把变量值同步回主内存;

volatile类型变量

当一个变量定义为volatile以后,它将具有两种特性,第一是保证此变量对全部线程的可见性,当一个线程修改了这个变量的值以后,对于其余线程而言是马上得知的。当时java里面的运算非原子操做,致使volatile变量的运算在并发环境下同样时不安全的。并发

volatile变量只能保证可见性,在不符合下面两条分这的运算场景中,依然要经过加锁。app

  1. 运算结果并不依赖变量的值,或者可以保证只有单一线程修改变量的值;
  2. 变量不须要于其余的状态变量共同参与不变约束。

volatile第二个语义是禁止重排序。volatile的读操做性能消耗与普通变量几乎没有差异,写操做性能会差一点,须要插入内存屏障来禁止重排序。性能

java内存模型的特征是:原子性、可见性、有序性。操作系统

  • 原子性:由java内存模型来直接保证的原子操做包括,read、load、assign、use、store、write;synchronized块之间也具有原子性。
  • 可见性:是指当一个线程修改了共享变量的值,其余线程可以马上得知这个改变。除了volatile以外,synchronized和final也能实现可见性。
  • 有序性:java自己的有序性,若是在本线程中观察,全部操做都是有序的,若是在一个线程观察另外一个线程,全部操做都是无序的。java语言提供了synchronized和volatile来保证线程之间的有序性。synchronized经过保证一个变量在同一时刻只有一个线程能对其进行lock操做保证的;volatile经过禁止重排序实现。 若是java内存模型中全部的有序性都仅仅靠volatile和synchronized来完成,那么咱们编写java代码将会很是繁琐。事实上咱们在编写java并发代码的时候并无感受到这一点,这是由于java语言中的happens-before原则。happens-before原则是JMM最核心的概念。

从JMM设计者的角度,在设计JMM是须要考虑到两个关键因素:线程

  • 程序员对内存模型的使用:程序员但愿内存模型,易于理解,易于编程。程序员但愿基于一个强内存模型来编写代码。
  • 编译器和处理器对内存模型的实现:编译器和处理器但愿对他们的约束越少越好,编译器和处理器但愿一个弱内存模型。

happens-before规则以下:

  1. 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序;
  2. 管程锁定原则(Monitor Lock Rule):一个unlock操做先行发生于后面对同一个锁的lock操做;
  3. volatile变量规则(volatile variable Rule):对一个volatile变量的写操做先发生于后面对这个变量读;
  4. 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每个动做;
  5. 线程终止规则(Thread Termination Rule):线程中的全部操做都先发生于对线程的终止检测,Thread.isAlive
  6. 线程中断规则(Thread Interruption Rule):对线程的interrupt方法的调用优先发生于被中断线程的代码检测到中断发生。
  7. 对象终结操做(Finalizer Rule):一个对象的初始化完成,先行发生于它的finalizer
  8. 传递性(Transitivity):A先行发生于B,B先行发生于C,A先行发生于C

线程的实现

实现线程主要有3中方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。

  1. 使用内核线程实现 内核线程就是直接由操做系统内核(Kernel)支持的线程,这个线程由内核完成线程切换,内核经过操纵调度器(Schedule)对线程进行调度,并负责将线程的任务映射到各个处理器上。咱们的程序通常不会直接使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程,轻量级进程就是咱们一般所说的线程。因为是基于内核线程实现的,因此各类线程操做(建立、析构、同步)都须要进行系统调用。而系统调用的代价较高,须要在用户态和内核态中来回切换。此外每一个轻量级进程都须要一个内核线程的支持,所以一个系统支持轻量级进程的数量是有限的。
  2. 使用用户线程实现 从广义上说,一个线程不是内核线程就能够认为是用户线程。而狭义上的用户线程指的是彻底创建在用户空间的线程库上,系统内核不能感知线程存在的实现。

线程调度

线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度和抢占式线程调度

线程状态切换

java定义了5种线程状态,在任意一个时间点,一个线程只能有且只有其中一中状态

  • new:建立后未执行;
  • runable:包括操做系统状态的running和ready;
  • waiting:无限期等待,处于这种状态的线程不会被分配cpu执行时间,Object.wait()、Thread.join()没有设置时间、LockSupport.park()方法;
  • Timed waiting:处于这种状态的线程不会被分配cpu执行时间,无需被其余线程唤醒,必定时间后会由系统唤醒。Object.wait()、Thread.join()设置时间、Thread.sleep()等。
  • blocked:阻塞状态,和waiting不一样的是,blocked状态在等待得到一个排它锁,在一个线程放弃这个锁的时候发生。
  • Terminal:线程执行结束。
相关文章
相关标签/搜索