Java并发编程(二)——Java并发底层实现原理
来源:互联网 发布:java输出正三角形 编辑:程序博客网 时间:2024/06/10 23:03
Java代码会被编译后变成Java字节码,字节码会被类加载器加载到JVM中,JVM执行字节码,最终转化成汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
volatile
在多线程并发编程中,synchronized和volatile都很重要,volatile是轻量级的synchronized,它在多处理器的开发中,保证了共享变量的可见性。*可见性指一个线程修改这个共享变量时,另外的线程能够读到这个修改的值。*volatile的成本更低,不会引起线程上下文的切换和调度。
volatile的定义和实现原理
Java语言规范中定义:Java编程语言允许线程访问共享变量,为了确保共享变量能被准备和一致地更新,线程应该确保通过排他锁单独获取这个变量。
要了解volatile的实现原理,首先要了解一些CPU的术语和说明
有volatile修饰的变量,进行写操作的时候会出现lock指令,lock指令在多核处理器下引发两件事情
- 将当前处理器缓存行的数据写回到系统内存
- 这个写回内存的操作会使其它CPU里缓存了该内存地址的数据无效
为了提供处理速度,处理器不直接和内存通信,而是通过高速缓存工作。如果对声明了volatile的变量进行了写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量所在的缓存行的数据写回到系统内存。
就算写会了系统内存,其它处理器缓存的值还是旧的。所以在多处理器下,为保证缓存的一致性,会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效,当处理器对这个数据进行操作时,会重新从系统内存中把数据读到处理器缓存里。
synchronized的实现原理
synchronized是Java的重量级锁,但是JDK1.6优化之后,也不是那么重量级了。
synchronized实现同步的基础:Java中的每一个对象都可以作为锁
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法块,锁是synchronized括号里配置的对象
JVM基于进入和退出monitor对象来实现方法同步和代码块的同步。代码块的同步是基于monitorenter和monitorexit指令实现的,而方法同步是使用另一种实现的。
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit指令是插入到方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之相关,当一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令,就会尝试去获取对象所对应的monitor的所有权,即锁。
Java对象头
synchronized用的锁存在Java对象头里面。如果对象是数组类型,则用3个字宽;如果是非数组则2个字宽。在32位虚拟机里,一个字宽等于4个字节,即32Bit。
Mark Word是定长但是非结构的,会随着锁标志位的变化而变化
默认存储结构
运行期间
锁升级与对比
Java为了减少获取锁和释放锁的性能消耗,引入偏向锁,轻量级锁。锁一共有四种状态,级别从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态。这几种状态会随竞争情况升级,但是不能降级。
偏向锁
HotSpot的作者发现,锁不仅不存在多线程的竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存放锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁或解锁,只需要简单测试一下对象头的Mark Word是否存储着指向当前线程的偏向锁。如果成功,则线程已经获得了锁;如果失败,需要在测试Mark Word中偏向锁标志是否为1。如果没有设置,采用CAS竞争锁;如果设置了,采用CAS将对象头的偏向锁指向当前线程。
偏向锁的撤销:偏向锁是一种等到竞争出现才释放锁的机制,所以当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
偏向锁在Java6和Java7中默认是启动的
轻量级锁
加锁
线程在执行同步块之前,JVM会先在当前栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,称为Displaced Mark Word。然后线程尝试使用CAS将对象头中 的Mark Word 替换成指向锁记录的指针。如果成功,当前线程获得锁;如果失败,表示其它线程竞争锁,当前线程便尝试使用自旋来获取锁。
解锁
解锁时,会使用原子的CAS操作将Displaced Mark Word替换回对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁会膨胀为重量级锁。
因为自旋会消耗CPU,为了避免无用的自旋,一旦锁升级为重量级锁就不会恢复到轻量级锁状态。当锁处于重量级状态时,其它线程试图获取锁时,都会被阻塞,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程会开始新一轮竞争。
锁的优缺点对比
- Java并发编程(二)——Java并发底层实现原理
- Java并发编程(二): Java并发机制的底层实现原理
- 并发编程笔记(二):Java 并发机制的底层实现原理
- 《Java并发编程的艺术》笔记二——Java并发机制的底层实现原理.md
- java并发编程的艺术【二】java并发机制的底层实现原理
- Java并发机制的底层实现原理(二)
- java并发底层实现原理
- Java并发机制底层——Volatile的实现原理
- Java并发机制底层——synchronized的实现原理
- 二、Java并发机制的底层实现原理
- 《Java并发编程的艺术》第二章——Java并发机制的底层实现
- Java并发-Java并发机制的底层实现原理
- JAVA并发机制的底层实现原理
- java并发机制的底层实现原理
- Java并发机制的底层实现原理
- Java并发编程的艺术--第二章<Java并发机制的底层实现原理>
- Java并发编程系列之一:并发机制的底层原理
- Java并发编程系列之一:并发机制的底层原理
- 树链剖分模板
- (转)谭志勇、赵微:区块链技术在中国商品交易市场的应用与发展
- 5.常用操作符-2017/08/13--1
- 正则表达式——匹配字符问题
- 13.调试
- Java并发编程(二)——Java并发底层实现原理
- 算法复杂度对数阶O(logn)详解
- Windows配置solr
- 利用互信息比较不同的聚类结果
- 对于所有对象都通用方法的解读(Effective Java 第三章)
- 第四章 第五节:INSERT...SELECT
- JQuery中serialize() 序列化
- 正则表达式-Regex
- word公式编号及交叉引用技巧