一、偏向锁->轻量级锁->重量级锁的说明
1、偏向锁
含义:当线程A第一次竞争到锁的时候,通过操作修改MarkWord中的偏向线程ID,锁变为偏向模式
作用:不存在其他线程竞争的情况下,不需要反复的获得和释放锁,减少性能的消耗
场景:有且只有一个线程访问的情况
2、轻量级锁
含义:多线程竞争,但是任一时刻最多只有一个线程竞争
作用:有线程来参与竞争,但是获取锁的冲突时间极短,本质就是CAS,避免线程的阻塞
场景:有多个线程来交替访问进行竞争
3、重量级锁
含义:直接到操作系统级别,使用monitor管程
场景:多个线程来访问竞争
二、用户态与核心态的切换
用一个例子来理解下,我们的应用程序需要从磁盘读取某个文件的数据,此时并不是直接从磁盘加载到应用内存中,而是:
先将数据从「磁盘」复制到「内核 Buffer」-内核空间
再将数据从「内核 Buffer」复制到「用户 Buffer」 -用户空间
用户空间中的代码被限制了只能使用一个局部的内存空间,我们说这些程序在用户态(User Mode) 执行。内核空间中的代码可以访问所有内存,我们称这些程序在内核态(Kernal Mode) 执行。
以上是对用户态和核心态的解释,接下来我们要明白java 的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态与核心态之间切换,这种切换是需要消耗大量的系统资源的(因为用户态与内核态都有客自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作)。
在Java早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(moni tor)是依赖于底层的操作系统的Mutex Lock(系统互斥量)来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个 Java 线程需要操作系统切换 CPU 状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因,在Java6之后,为了减少获得锁和释放锁所带来的性能消着毛,引入了轻量级锁和偏向锁。
三、锁的升级过程
无锁->偏向锁->轻量级锁->重量级锁
1、无锁状态 -> 偏向锁
首先当处于无锁状态时,MarkWord中存储的是对象的hash code等信息,偏向锁为0,此时线程1进入,发现没有记录线程ID信息,则直接CAS操作将自己的线程ID设置进去,设置成功则获取到偏向锁,偏向锁标志为改为1;失败则要升级为轻量级锁;
2、偏向锁 -> 轻量级锁
出现两个及以上线程竞争时(已经偏向给了一个线程,此时又出现了另外一个线程竞争,即升级),开始升级为轻量级锁。
3、轻量级锁 -> 重量级锁
设置失败,则证明有竞争,此时会进入自旋状态。
线程超过10次自旋, 或者自旋线程数超过CPU核数的一半,(JDK1.6之后,加入自适应自旋 Adapative Self Spinning , JVM自己控制)。就会升级为重量级锁。