在 多线程知识梳理(2) - synchronized 基本使用 中,咱们介绍了使用重量锁来实现的synchronized
。今天,咱们就来一块儿学习一下在JDK 1.6
以后,对synchronized
所采起的一系列优化措施。html
在介绍优化方法以前,咱们须要介绍两个重要的概念Java
对象头和Monitor
: 编程
在 Java&Android 基础知识梳理(3) - 内存区域 中介绍内存区域的时候,对于一个Java
对象所占的内存区域是这么介绍的: 安全
32
位的虚拟机为例,下图就是锁状态标志位所对应的数据结构含义:
Monitor
是线程私有的数据结构,因为一个线程可能进入多个不一样的同步方法,这些方法有可能会关联到不一样的Monitor
,所以每个线程都有一个可用的Monitor
列表,同时还有一个全局的可用列表,Monitor
数据结构包括如下成员变量:数据结构
Owner
:初始时为空表示当前没有任何线程拥有该Monitor
,当线程成功拥有该锁后保存线程惟一标识,当锁被释放时又设置为空。EntryQ
:关联一个系统互斥锁,阻塞全部试图得到Monitor
可是最终失败了的线程。RcThis
:表示blocked
或waiting
在该Monitor
上的全部线程的个数。Nest
:用来实现重入锁的计数。HashCode
:保存从对象头拷贝过来的HashCode
值。Candidate
:用来避免没必要要的阻塞或等待线程唤醒,由于每一次只有一个线程可以成功拥有锁,若是每次前一个释放锁的线程唤醒全部正在阻塞或等待的线程,会引发没必要要的上下文切换(从阻塞到就绪而后由于竞争锁失败又被阻塞)从而致使性能严重降低。Candidate
只有两种可能的值:0
表示没有须要唤醒的线程,1
表示要唤醒一个继任线程来竞争锁。在JDK 1.6
以后,它对于锁进行了一系列的优化措施,主要包括:自适应自旋锁、锁消除和锁粗化。多线程
因为线程的阻塞和唤醒须要CPU
从用户态转换成核心态,而频繁的阻塞和唤醒对CPU
来讲是一件负担很重的工做。并发
所以,咱们在发现锁已经被其它线程占有时,并不直接让当前线程进入阻塞状态,而是让线程执行一段无心义的循环,待循环结束后,如何仍然没法获取到锁,那么才进入阻塞状态。性能
决定自旋锁性能的关键在于自旋次数的选择,在JDK 1.6
以后,引入了自适应自旋锁,它会根据前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定新的自旋次数。学习
当JVM
检测到不可能存在共享数据竞争,会对同步锁进行锁消除。优化
在使用同步锁的时候,须要让同步块的做用范围尽量地小,仅在共享数据的实际做用域中才进行同步,这样作的目的是为了使须要同步的操做数量尽量缩小,若是存在锁竞争,那么等待锁的线程也能尽快拿到锁。操作系统
然而,若是一系列连续加锁解锁操做,可能会致使没必要要的性能损耗,因此有时能够将多个连续的加锁、解锁操做链接在一块儿,扩展成一个范围更大的锁。
在JDK 1.6
以前,锁只有两种状态:无锁状态和重量级锁状态,而在这以后增长为四种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,这种改进基于两点考虑:
Mutex Lock
实现,操做系统实现线程之间的切换须要从用户态切换到内核态,切换成本很高。须要注意,对于锁的这四种状态,它们会随着竞争的激烈而逐渐升级,可是它只容许锁升级,不容许锁降级。
无锁状态和重量级锁状态都比较好理解,下面咱们主要介绍新增的两种锁状态:偏向锁状态和轻量级锁状态。
整个转换的流程图以下所示,在后面的介绍中能够参考:
引入偏向锁的目的是:在无多线程竞争的状况下,尽可能减小没必要要的轻量级锁执行路径,它的理想状况下是在无竞争时把整个同步都去掉,连CAS
操做都省略。
偏向锁的意思是这个锁会偏向于第一个得到它的线程,若是在接下来的执行过程当中,该锁没有被其它线程获取,则持有偏向锁的线程将永远不须要再进行同步。
获取偏向锁的前提条件是synchronized
所修饰的对象处于可偏向状态
01
1
当知足前提条件时,再去判断对象的Mark Word
中的线程ID
是否指向当前线程
CAS
操做竞争锁
Mark Word
的线程ID
替换为当前线程ID
,接着执行同步代码块释放偏向锁的前提条件是其它的线程在竞争偏向锁的过程当中出现了失败的状况,而且偏向锁的释放须要等待到达全局安全点。
当知足释放偏向锁的前提条件时,首先会暂停拥有偏向锁的线程,接着判断锁对象是否处于被锁定的状态,决定锁标志位下一步的状态:
01
,偏向锁状态置为0
,表示它处于无锁,且不可偏向状态。00
,表示它处于被轻量级锁定的状态。引入轻量级锁的目的是:在无多线程竞争的状况下,减小传统的重量级锁使用操做系统互斥量产生的性能消耗。
获取轻量级锁的前提条件时当前对象处于无锁状态,
01
0
JVM
首先将在当前线程的栈帧中创建一个名为锁记录(Lock Record
)的空间,用于存储对象目前的Mark Word
的拷贝,以后JVM
利用CAS
操做尝试将对象的Mark Word
更新为指向Lock Record
的指针:
00
,表示处于锁定的状态,以后执行同步操做。Mark Word
是否指向当前线程的栈针10
,后面等待的线程将会进入阻塞状态。轻量级锁的释放也是经过CAS
操做来进行的:
Displaced Mark Word
中的数据。CAS
操做将取出的数据替换到当前对象的Mark Word
中:对于轻量级锁,它性能提高的依据是默认"对于绝大部分的锁,在整个生命周期内是不会存在竞争的",若是不符合这种状况,那么除了互斥的开销外,还有额外的CAS
操做,这样轻量级锁比重量级锁更慢。
Java 并发编程:Synchronized 底层优化(偏向锁、轻量级锁) 死磕 Java 并发 -----深刻分析 synchronized 的实现原理