锁的实现原理以及锁的升级过程

锁就是为了解决多个并行执行分支对同一资源进行同步访问的问题。
synchronized和lock锁
synchronized又称为内置锁、监视锁。
synchronized是java中的关键字,可以理解为java中内置的功能,所以为内置锁。
synchronized又通过monitorenter和monitorexit两个指令来控制获取锁和释放锁,monitor为监视的意思,所以synchronized又称为监视锁。
内置锁的特点:
1、可重入锁。可重入的意思是如果一个线程持有一把监视锁时,如果需要再次获得该锁,不需要进行锁的竞争,可以直接获得该锁。

public synchronized void test() {
    test1();
  }
public synchronized void test1() { 
  }

当test持有一个监视锁时,调用test1,这时候就可以直接获得该锁,这样是对的。
2、是阻塞的。如果线程一已经持有锁,线程二想持有锁肯定是失败的,那么他就会进入到阻塞队列。
3、不公平锁。synchronized是不公平的,当一个线程持有锁而迟迟不释放锁,那么阻塞队列中的线程会越来越多,假设现在阻塞队列中有3个线程,分别为1号、2号和3号,他们进入阻塞队列的顺序与序号相同,但是当释放锁时,按理说应该是1号获得锁,但事实不是如此。所以synchronized是不公平的,而公平的则是按照线程进入阻塞队列的顺序来获得锁。
内置锁的状态:
内置锁的状态其实还是因为java1.6之后对synchronized的优化锁划分出来的,1.6之前都是jvm内部使用monitorehter和monitorexit指令,而这两个指令非常依赖操所系统提供的互斥信号量。
可以先看看锁存放的位置。
每个对象在内存中分为三个部分:对象头、实例数据和对齐填充。而锁信息就存放在对象头中,由于对象头的长度和具体机器字长相关,一般来说目前存在两种字长,32和64位,对象头的格式如下图:
锁的实现原理以及锁的升级过程_第1张图片
锁的信息就存放在MarkWord这个区域内。
类型指针,指向当前对象类型,表明这个实例是什么类型的;数组长度是可选的,只有当前对象是数组的时候才会存在,表明数组的长度。最后我们来看看最复杂的Mark Word,它的格式如下:
锁的实现原理以及锁的升级过程_第2张图片
所谓状态,指的就是Mark Word的最后两位,图中的标志位。
可GC 标志位:11
这个状态就是Mark Word最后两个Bit是11,如果设置了这个标记,那么表示当前这个类是可GC的,前面的bitfields里面的内容已经不重要了,因为这个类马上就要被回收。
1、无锁 标志位:01
我们注意到无锁和偏向锁都使用了01标志位,这样没有办法,我们只好再往前占一个bit来区分,这个位用来标识偏向锁是否禁止,无锁状态就表示当前对象禁止偏向;
2、偏向锁 标志位:01
由于此时这个对象是可以偏向的,它存在三种情况:
(1): 匿名偏向(Anonymously biased)
表示当前还没有线程偏向这个对象,第一个试图获取锁的线程可以使用CAS指令去改变锁对象的Mark Word指向自己。这个状态是可偏向对象锁的初始状态。
(2): 可重偏向(Rebiasable)
epoch字段无效,可以理解为之前这个锁对象偏向于某个线程,但是这个线程已经退出了临界区,这个时候如果另外一个线程来获取锁,可以使用CAS指令去改变锁对象的Mark Word指向自己。
(3): 已偏向(Biased)
epoch字段有效,表示锁对象当前已经偏向某一个线程。
以上内容参考:偏向锁
3、轻量级锁 标志位:00
偏向锁存在竞争时,进入轻量级锁的状态,此时获取锁的线程开始自旋等待。
4、重量级锁 标志位:10
竞争锁的各个线程开始使用系统的信号量做同步,回到最原始的状态。


偏向锁的想法是线程在获得了锁即得到了监视对象之后,让监视对象“偏向”自己,等到再次想获得锁的时候可以直接获得,不必再走锁的逻辑。线程获得锁,会在a线程的的栈帧里创建lock record(锁记录变量),则在锁对象的对象头里和lock record里存储a线程的线程id.以后该线程的进入,就不需要cas操作,只需要判断是否是当前线程。
如何获得偏向锁?
1、检查偏向锁是否打开,如果没有打开那么就去获得轻量级锁。
2、判断锁的状态。
(1)判断锁的状态是否为匿名偏向,如果是,那么让锁偏向线程。如果失败了就去获得轻量级锁。
(2)判断锁的状态是否为可重向偏向,如果是,那么让锁偏向线程。如果失败了就去获得轻量级锁。
(3)判断锁的状态是否为已偏向,如果是就去获得轻量级锁。
无锁状态和偏向锁的标志位都是01,为什么无锁状态下存储hashcode,而偏向锁没有了呢?
因为添加了锁信息,他就需要存放线程ID等信息,没有地方存放hashcode了,所以经过hash计算的对象无法作为偏向锁,轻量级锁和重量级锁都会在线程的栈里面创建一块专门的空间Displaced Mark Word,用于在获得锁的时候,复制“锁”的对象头里面的Mark Word内容,把当前的线程ID写进Mark Word;而在释放锁的时候,再从Displaced Mark Word复制回锁的Mark Word里面。
而想计算偏向锁的hashcode时,需要撤销偏向锁,并且会升级为重量级锁。


锁的升级过程:
无锁状态 —>偏向锁—>轻量级锁—>重量级锁
1、当想保证线程安全而且经常只有一个线程使用资源的话就会从无锁状态升级为偏向锁。
2、当偏向锁出现竞争现象,就会升级为轻量级锁。
3、轻量级锁出现竞争时会自旋等待,如果竞争过大或者自旋等待时间过长,就会从轻量级锁升级为重量级锁。

你可能感兴趣的:(锁的实现原理以及锁的升级过程)