[Thread] Synchronized
1.Monitor对象
Monitor对象被称为管程或者监视器锁。
在Java中,每一个对象实例都会关联一个Monitor对象。
这个Monitor对象既可以与对象一起创建销毁,也可以在线程试图获取对象锁时自动生成。
当这个Monitor对象被线程持有后,它便处于锁定状态。Synchronized的基本原理就是能否获取Monitor的所有权。
monitor
ObjectMonitor() {
_header = NULL;
_count = 0; //用于记录线程获取锁的次数,成功获取到锁后count会加1,释放锁时count减1
_waiters = 0,
_recursions = 0;// 线程重入次数
_object = NULL; // 存储Monitor对象
_owner = NULL; // 持有当前线程的owner
_WaitSet = NULL;// wait状态的线程列表
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 单向列表
FreeNext = NULL ;
_EntryList = NULL ;// 处于等待锁状态block状态的线程列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
2.对象头
在对象头的MarkWord中主要存储了对象自身的运行时数据,例如哈希码、GC分代年龄、锁状态、线程持有的锁、偏向线程ID等
当对象为偏向锁时,Mark Word存储了偏向线程的ID;
当状态为轻量级锁时,Mark Word存储了指向线程栈中Lock Record的指针;
当状态为重量级锁时,Mark Word存储了指向堆中的Monitor对象的指针。
3.锁升级
偏向锁
偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,即将对象头中Mark Word的标记位改为1,并且在Mark Word中记录该线程的ID。
轻量级锁
Mark Word中的部分字节CAS更新指向线程栈中的LockRecord,如果更新成功,则轻量级锁获取成功_,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。
JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁MarkWord的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把MarkWord中的锁标志位改成00,可以执行同步代码。如果保存失败,表示抢锁失败
自旋锁
空循环一般不会执行太多次,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入同步代码。如果还不能获得锁,那就会将线程在操作系统层面挂起,即进入到重量级锁。
重量级锁
重量级锁通过对象内部的monitor实现(见上文的MonitorObject模式)
monitor的本质是依赖于底层操作系统的MutexLock实现,操作系统实现线程间的切换是通过用户态与内核态的切换完成的,而切换成本很高
MutexLock最核心的理念就是尝试获取锁.若可得到就占有.若不能,就进入睡眠等待
4.其他概念
锁消除
JVM在编译时通过对运行上下文的描述,去除不可能存在共享资源竞争的锁,通过这种方式消除无用锁,即删除不必要的加锁操作,从而节省开销
锁膨胀
将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁
如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁