synchronized的锁有三种,重量级锁,轻量级锁,偏向锁。下面先从重量级锁开始讲。
1.重量级锁
要理解重量级锁,先了解两个概念,对象头和Monitor
1.1对象头
先看下对象头图解
数组对象
其中 Mark Word 结构为
64 位虚拟机 Mark Word
2.Monitor
Monitor被翻译成监视器或管程,其实就是真正的锁
Monitor是由操作系统用C语言创建的一种数据结构,它由这三部分Owner,EntryList,WaitSet组成。
Owner:当前正在执行的线程,锁的持有者。
EntryList:这里面是未获取到锁的线程,处于阻塞状态。
WaitSet:线程获取到了锁,但是未满足执行条件,调用wait()方法进入WaitSet等待。
synchronized(obj){
//临界代码区
}
当使用重量级锁时
第一个线程Thread1到来时,它会将obj对象和Monitor对象关联,通过用obj的MarkWord前62为存储Monitor对象的地址,后两位设置成10,声明他为重量级锁。
当第一个线程Thread1还没执行完成,第二个线程Thread2到来时,它通过存储在obj的MarkWord内Monitor对象地址找到Monitor对象,发现它的Owner不为空,这时它进入阻塞队列EntryList,当第一个线程仍未执行完,后续到来的线程同上。
当一个线程执行完了,它会将Monitor对象的Owner置为null,唤醒在EntryList中阻塞的队列。
2.轻量级锁
使用Monitor对象是非常耗费性能的,所以如果一个对象虽然有多线程访问,但是多个线程访问的时间是错开的,也就是不存在竞争,那么可以使用轻量级锁优化。
看上图中obj对象的图解
HashCode 哈希码
Age 分代年龄
Bias 是否为偏向锁(占1bit,1为偏向锁)
01 锁类型
001无锁
101 偏向锁
00 轻量级锁
10重量级锁
使用轻量级锁时,当第一个线程Thread0执行到同步代码块时,它首先会在自己的虚拟机栈的栈帧中创建一个锁记录对象(LockRecord),让Object Refence指向锁对象,并尝试用CAS替换obj对象的MarkWord,将MarkWord存入锁记录
如果CAS替换成功,对象头存储了锁记录的地址和状态,表示由该线程给对象加锁
如果CAS失败,有两种情况
如果其他线程已经持有了该Object轻量级锁,这时表明有竞争,进入锁膨胀的过程
如果自己执行了synchronized的锁重入,那么再添加一个LockRecord作为重入的计数
解锁时,如果有取值为null值得锁记录,表示有重入,这时重置锁记录,表示重入计数减一
当不为null时,这是会CAS将MarkWord得值恢复给对象头
成功,解锁成功
失败,说明,轻量级锁升级为重量级锁,进入重量级锁得解锁流程
当Thread0还未执行完,Thread1线程来了,这时他发现Object对象的MarkWord区域已经被其他线程占了,这时发生了竞争,发生锁膨胀,轻量级锁升级为重量级锁
即为Object对象申请Monitor锁,让Object指向重量级锁
然后让自己进入EntryList中阻塞
当Thread0退出同步代码块解锁时,使用CAS将MarkWord恢复到对象头,失败,这是会进入重量级锁的解锁流程,即按照Monitor地址找到Monitor,设置Owner为空,唤醒EntryList的线程。
3.自旋优化
重量级锁发生竞争时,还可以使用自旋进行优化,当Thread0占有了锁时,Thread1再来,这是他不会立即阻塞,它会自旋重试几次,如果成功了,他就不要进入阻塞状态,也有自旋失败的情况。
再Java6之后自旋是自适应的,自旋成功高,它就会多自旋几次,反之,就会少自旋几次,甚至不自旋,总之,比较智能。
自旋要多核CPU才能发挥优势
Java7之后不能控制是否开启自旋功能
4.偏向锁
轻量级锁在只有自己这个线程时,它每次操作还要进行CAS操作
Java6引入了偏向锁来做进一步优化,只有第一次使用CAS操作时将线程ID设置到对象头的MarkWord中,之后发现这个线程ID是自己的就表示没有竞争,不用CAS,以后只要不发生竞争,这个对象就归该线程所有。
偏向锁是默认开启的,也是默认延迟的不会在程序时立即生效,如果想避免延迟,可以加VM参数
将其他线程使用偏向锁时,将升级为轻量级锁
调用wait / notify 时,会升级为重量级锁,因为要使用到Monitor
批量重偏向
如果对象被多个线程访问,但没有发生竞争,这时偏向线程T1的对象,仍有机会偏向线程T2,重偏向会重置对象的ThreadID
当撤销偏向锁阈值达到20次后,jvm会这样觉得,我是不是偏向错了呢,于是会给这些对象加锁时重新偏向至加锁线程
批量撤销
当撤销偏向锁阈值达到40次后,jvm会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变得不可偏向,新建的对象也是不可偏向的。