一:基本原理
Java对象在内存中由两部分组成 : 1 是成员变量
2 是对象头
,以32位虚拟机介绍:此时对象头是64位
,即8字节
其中32个字节代表 mark word
另外32个字节代表klass word
分别是什么意思呢?
1 klass word
代表对象的类型
,即student、teacher这种
2 mark word
中,hashcode
占 25位,Age
分代年龄 4位,Biased_lock
代表是否是偏向锁,最后两位代表加锁状态
多线程访问三种情况
1 单线程访问
2 多线程交替访问
3 多线程竞争激烈
锁升级主要依赖存在Java对象中的Mark Word中的锁标识位和释放偏向锁标识位
无锁 : 001
偏向锁:101
MarkWord 存储的是偏向的线程ID
轻量锁: 00
MarkWord 存储的是线程栈Lock Record
指针
重量锁: 10
MarkWord 存储的是指向堆中monitor对象的指针
二:锁升级之偏向锁
缺点
:每次发生锁重入时,每次重入都需要cas检查
例如 方法1 调用方法2 方法2调用方法3,都是同一把锁,可以成功,但是每次都会新产生一个锁记录对象,和对象头交换,虽然交换失败,但是知道这个锁是自己加的,所以还是会拿到锁,这样每次都进行cas
操作依然有性能损耗
有没有办法避免这种损耗呢?
就是偏向锁
,只有第一次使用CAS
将线程ID设置到对象(threadId 替换 markword
),之后发现这个线程id是自己的就表示没有竞争,不用重新CAS,
等于说这把锁是这个线程专用的了
三:锁升级之轻量锁
多线程访问时间错开,没有竞争
,可以用轻量锁优化,语法依然是synchronized,使用时优先使用轻量锁,加锁失败,或竞争激烈才会用重量锁
基本原理
当线程a调用一个方法时,会产生一个锁记录对象
锁记录对象包含两部分{
1 对象指针
,记录加锁对象的地址
2 存储将来加锁对象的mark word
}
如下图,左为锁记录对象,右为锁对象
锁记录对象会将对象指针
与锁对象的mark word
交换,这是一个CAS
操作,此时,锁对象的mark word 中不再是之前提到的hashcode等信息,而是锁记录地址
四:锁膨胀
轻量级锁竞争失败,进入锁膨胀,升级为重量级锁,如果尝试加轻量锁失败,代表已经有其他线程加了轻量锁(有竞争),这时需要锁膨胀,将轻量锁升级为重量锁
例如现在thread1已经加了锁,现在来了一个thread2,想用轻量锁方式加锁,就想把自己的对象指针与markword交换,此时交换失败,因为此时后两位是00,不再是无锁状态
失败后进入锁膨胀
1 为object对象申请Monitor锁,让Object指向重量级锁地址
2 自己进入Monitor的EntryList bloked
3 markword后两位变成10
4 thread1 退出同步块解锁时,使用cas将mark word 的值恢复给对象头
5 失败这时会进入重量级解锁流程,即按照monitor地址找到monitor对象,设置owner为null,唤醒
EntryList 的bloked线程
自旋优化:
一次失败后不立即进入阻塞,而是再尝试几次,在尝试的过程中
如果持有锁的线程退出了同步块,当前线程就可以避免阻塞