[Java并发系列] 1.Java并发机制的底层实现

在Java并发实现的机制中,大部分的容器和框架都是依赖于volatile/synchronized/原子操做实现的,了解底层的并发机制,对于并发编程会带来不少帮助编程

1、 synchronized的应用

synchronized在多线程并发编程中已是一个元老级的存在,一般被称做是重量级锁。既然是经常使用的一种锁,那么就须要对它的底层实现有深刻的了解。缓存

1. synchronized的实现原理

当一个线程在访问同步代码块时,就必需要先获取该代码块中对象的锁,退出或者抛出异常时,就必需要释放锁。synchronized的同步实现,是JVM基于进入和退出同步对象的Monitor对象来实现方法同步和代码块同步的。
每一个Monitor对象都有与之关联的monitor,当且仅当monitor被持有后,它才会处于锁定状态。同步代码是使用monitorenter和monitorexit指令实现的。monitorenter指令时在代码编译后插入到同步代码块的开始位置,monitorexit指令则是插入到方法的结束和异常处。当线程执行到monitorenter时,会尝试去获取对象的monitor的全部权,即尝试获取对象的锁。多线程

2. synchronized的使用形式及意义
  • 修饰普通方法:锁是当前实例对象
  • 修饰静态方法:锁是当前类的Class对象
  • 修饰代码块:锁是synchronized括号里面配置的对象
3. 锁的升级

锁共有四种状态:无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态并发

偏向锁:当线程访问同步块并获取锁时,会在对象头和栈针中的锁记录中存储锁偏向的ID,之后该线程在进入和退出同步块时就不须要在使用CAS操做来进行加锁和解锁操做,而仅仅须要测试一下对象头中的Mark Word字段中存储的线程ID是不是当前线程便可。若是是,那么表示获取锁成功;若是不是,则须要检查对象头中偏向锁的字段是否设置为了1(表示当前锁是偏向锁),若是没有设置,则须要使用CAS来竞争获取锁,若是已经设置了,则尝试使用CAS将对象头的偏向锁ID指向当前线程。
轻量级锁;框架

  • 轻量级锁加锁:线程在执行同步块以前,首先会在本身的线程栈中建立一个用于存储锁记录的空间,并将对象中的Mark Word 赋值到锁记录中。而后线程尝试使用CAS操做将对象头中的Mark Word替换为指向锁记录的指针。若是成功,则表示当前线程获取锁,若是失败,则表示其余线程也在竞争锁,当前线程便使用自循的方式来获取锁。
  • 轻量级锁解锁:轻量级锁解锁时,会使用原子CAS操做将锁记录替换会对象头,若是成功,则表示没有竞争发生。若是失败,则表示当前锁存在竞争,锁就会膨胀为重量级锁。
4. 锁的对比
锁类型 优势 缺点 适用场景
重量级锁 线程竞争不使用自旋,不会浪费CPU 线程阻塞,响应时间慢 追求吞吐量,同步块执行的时间长的场景
轻量级锁 竞争的线程不会阻塞,提升程序的响应速度 若是线程长时间得不到锁,那么自旋就会浪费CPU 追求响应时间,同步块执行速度快
偏向锁 加锁和解锁不须要额外的资源消耗 若是线程间存在竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步场景

2、volatile的应用

volatile是轻量级的synchronized,volatile在多线程开发中保证了共享变量的可见性,所谓可见性,指的是当一个线程修改了共享变量以后,对于其余线程来讲,可以读到这个修改的值。编程语言

1. volatile的定义及实现原理

volatile定义:Java编程语言容许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保经过排他锁得到这个变量。
实现原理:当对声明了volatile的变量进行写操做时,JVM就会向处理器发送一条带有lock前缀的指令,将这个变量所在的缓存行的数据写回到系统内存中。同时,在多处理器的状况下,须要执行缓存一致性协议,即每一个处理器都须要经过嗅探总线上传播的数据来检查本身的缓存是否过时,当处理器发现本身缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置为无效,当处理器须要对这个数据进行操做时,再从系统内存中把数据读取到缓存行中。测试

2. 实现volatile的两条原则
  • 带有Lock前缀的指令会引发处理器缓存写回到内存;
  • 一个处理器的缓存写回到内存,会致使其余处理器的缓存无效。

3、原子操做的原理

见文章[并发编程系列]Java中的原子操做类线程

相关文章
相关标签/搜索