学习java虚拟机 - 内存模型与线程

学习java虚拟机 - 内存模型与线程

一、为什么

  Java虚拟机规范试图定义一种Java内存模型(Java Memory Model, JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存一致的内存访问效果。

 

 

二、主内存和工作内存

   Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中奖变量存储到内存和从内存中取出变量这样的底层细节。

  Java内存模型规定了所有变量都存储在主内存(Main Memory)中,每条线程有自己的工作内存(Working Memory), 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存的变量,线程间变量之的传递均需要通过主内存来完成。

 

 

 

 三、内存间的交互操作

  关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型重情义了一下8种操作来完成,虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的。

  1. lock(锁定): 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。

  2. unlock(解锁): 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他的线程锁定。

  3. read(读取): 作用于主内存的变量,它把一个变量的值从主内存传输到线程工作内存的工作内存中,以便随后的load动作使用。

  4. load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

  5. use(使用): 作用于工作内存的变量,它把工作内存中一个变量值传递给执行引擎。

  6. assign(赋值): 作用于工作内存的变量,它把从执行引擎接收到的值附给工作内存的变量。

  7. store(存储): 作用于工作内存的变量,它把工作内存中的变量的值传递到主内存中,以便后续的write操作使用。

  8. write(写): 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

 

 

四、volatile型变量

  1)保证该变量在各线程之间的可见性

    含义:某一线程修改volatile型变量时,其他线程能够知道

实现原理:线程在工作内存中修改volatile型变量后,jvm会将最新值刷入主内存中;线程在使用volatile型变量时,会先从主内存中读取该值,刷入工作内存中。

  2)禁止指令重排序优化

   volatile修饰的变量的语句之前的代码不能排在该语句之后,  该语句执行完时, 意味着之前的语句都已经执行完了, 从而形成了"指令重排无法越过内存屏障"的效果.

 

 

五、原子性、可见性与有序性

  原子性:java内存模型中提供的read, load, use, assign, store, write的操作是原子性的,这些操作不可分割。对于大范围的原子性,jvm使用lock,unlock操作控制,这两个操作并没有直接向用户提供,但可以使用字节码指令monitorenter,monitorexit(synchronize关键字)隐式提供。

  可见性:java内存模型是通过在修改变量值后将新值同步到主内存,在变量读取前从主内存刷新该值的方式来实现可见性。volatile, sychronized, final 关键字可以保证可见性。volatile变量与普通变量的区别,在于volatile变量在修改之后会立即同步到主内存。synchronized在unlock操作之后会强制将变量同步到主内存。final类型变量,在构造器初始化完之后,并且没有将"this"引用传递出去,就可保证其可见性。

  有序性: 如果在本线程观察,所有操作都是有序的,如果在其他线程观察,所有操作都是无序的。前半句指"线程内表现为串行语义", 后半句是指"指令重排"和"工作内存和主内存同步延迟"现象。volatile, sychronized关键字保证线之间操作的有序性。volatile会禁止指令重排,sychronized则保证一个变量一个时刻只允许一条线程对其进行lock操作。

 

 

六、先行发生原则

   java内存模型中的所有有序性除了依靠volatile和synchronized完成之外,还依赖于"先行发生"(happens-before)的原则。该原则是判断是否存在竞争、线程是否安全的主要依据,依赖这个原则,可以一揽子解决并发环境下两个操作之间是否可能存在冲突的所有可能问题。

  先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果说A先行发生于B操作之前,操作A产生的影响能被操作B观察到,"影响"包括修改了内存中共享变量的值、发送了消息、调用了方法等。

 

  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.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。

  6. 线程中断规则(Thread Interruption Rule):

   对线程interrupt()方法的调用先行于中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。      

  7. 对象终结规则(Finalizer Rule):

   一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

  8. 传递性(Transitivity):

   如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的操作。

 

  时间先后顺序与先行发生原则基本没什么关系,所以衡量并发安全问题时不要受时间顺序的干扰,一切必须以先行发生原则为准。

  

 

七、java线程状态转换

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

1)新建(New):  新建尚未启动的线程

2)运行(Runnable): 处于该状态的线程有可能正在运行,也可能正在等待CPU为它分配执行时间。

3)阻塞(Blocked): 线程竞争排他锁后并未获取到排他锁时会进入阻塞状态,处于该状态的线程在等待排他锁。

4)无限期等待(Waiting) / 有限期等待(Timed Waiting):

  处于Waiting状态的线程不会被分配CPU执行时间,它们要等待其他线程的显式唤醒。

  处于Timed Waiting状态的线程不会被分配CPU执行时间,不过无需等待被其他线程显式的唤醒,过一段时间它会自动被系统自动唤醒。

5)结束(Terminated): 已终结线程的线程状态。

     

 

学习资料:

  <深入理解java虚拟机>

 

 

posted @ 2019-08-27 00:35  timfruit  阅读(194)  评论(0编辑  收藏  举报