Java并发机制的底层实现原理
一、volatile
volatile 是轻量级的 synchronized ,它在多处理器开发中保证了共享变量的可见性,可见性是指当一个线程修改一个共享变量时,另一个线程能读到这个修改的值;
1 . 定义
Java 编程语言允许线程访问共享变量,为了确保共享变量能被准确个一致地更新,线程应该确保通过排他锁单独获得这个变量。如果一个字段被声明称 volatile,Java内存模型确保所有线程所看到的这个变量的值是一致的。
2 . 实现原则
(1)LOCK 前缀指令会引起处理器缓存回写到内存
(2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效
二、synchronized
1 . 实现同步的基础:Java中每个对象都可以作为锁
- 对于普通同步方法,锁是当前同步对象;
- 对于静态同步方法,锁是当前类的 Class 对象;
- 对于同步方法块,锁是 Syncgronized 括号里配置的对象;
2 . 实现原理
JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步,代码块同步是使用 monitorenter 和 monitorexit 指令实现的,monitorenter 指令事再编译后插入到同步代码块的开始位置,而monitorexit 是插入到方法结束处和异常处;
3 . 锁的升级与对比
锁一共有四种状态,级别从低到高依次是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,状态会随着竞争的增加而升级。但是锁可以升级但是不能降级,其目的是为了提高获得锁和释放锁的效率;
(1)偏向锁
当一个线程访问同步块时,会在对象头和栈帧中的所记录里存储偏向锁偏向的线程 ID ,以后该线程再进入和退出同步块时不需要进行 CAS 操作来加锁和解锁,只需要简单的测试一下对象头的 Mark Word 里是否储存指向当前线程的偏向锁
(2)轻量级锁
轻量级锁加锁 :线程执行同步块之前, JVM 会先在当前线程的栈帧中创建用于储存所记录的空间,并将对象头中的 Mark Word 复制到所记录中,然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为锁的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁;
轻量级解锁 :解锁时使用原子的 CAS 操作将 Displaced Mark Word 替换回到对象头,如果成功,表示没有竞争发生,如果失败,表示当前所存在竞争,锁回膨胀成重量级锁
(3)锁的优缺点
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁解锁开销低 | 线程竞争增加锁撤销消耗 | 只有一个线程访问同步块场景 |
轻量级锁 | 竞争的线程不会阻塞,提高程序相应速度 | 得不到锁的竞争线程会自旋消耗 CPU | 追求响应时间和执行速度的场景 |
重量级锁 | 线程竞争不使用自旋,不会消耗 CPU | 竞争线程阻塞,响应时间缓慢 | 追求吞吐量和同步块执行速度较长 |
三、原子操作的实现原理
1 . 处理器实现原子操作
处理器使用基于缓存锁和总线锁的方式来实现来实现多处理器之间的原子操作;
(1)使用总线锁保证原子性
总线锁 :使用处理器提供的一个 LOCK # 信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以单独共享内存;
(2)使用缓存所保证原子性
缓存锁定是指内存区域如果被缓存在处理器的缓存形中,而且在 Lock 操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声言 LOCK # 信号,而不是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为,缓存一致性会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使缓存无效;
2. Java 如何实现原子操作
Java 通过锁和循环 CAS 的方式来实现原子操作
(1)使用循环 CAS 来实现原子操作
JVM 中的 CAS 操作利用了处理器提供的 CMPXCHG 指令实现,自旋 CAS 实现的基本思路为循环进行 CAS 操作直到成功为止,从 Java 5 开始,JDK 并发包中提供了一些类如:AtomicBoolean ,AtomicInteger 和 AtomicLong;来支持原子操作 ;
(2)CAS 实现原子操作的三大问题
ABA 问题 :解决思路为使用版本号,每次更新的时候版本号加一,就会变成:1A-2B-3A;从Java 5 开始 JDK Atomic 包中提供了类AtomicStampedReference 来解决 ABA 问题,该类的 compareAndSet 方法会先检查当前引用是否是预期引用,当前标志是否是预期标志,如果都相等,则以原子方式将该引用和该标志的值设置为给定的更新值;
循环时间长开销大 :解决思路为 pause 指令,它可以延迟流水线指令,可以避免在退出循环的时候因内存顺序冲突而引起 CPU 流水线被清空,从而提高 CPU 执行效率;
只能保证一个共享变量的原子操作 :解决思路为使用锁,或者把多个共享变量合并成一个共享变量,Java 5 ,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放到一个对象里来进行 CAS 操作;
(3)使用锁机制来实现原子操作
处理偏向锁, JVM 实现锁的方式都使用了 CAS 操作,即当一个线程想进入同步块的时候使用了循环 CAS 的方式来获取锁,当他退出同步块的时候使用循环 CAS 释放锁;
还没有评论,来说两句吧...