monitor(监视器/管程)
java对象分三部分,
- 对象头
- 数据实例
- 填充
对象头分为
- 普通对象- markword(32bit)/klass word(32bit)(指向对应的class对象)
- 数组对象-多一个array length(32bit)数组长度
markword的结构
- hashcode(25) age(4) biased_lock:0(代表是否是偏向锁) | 01 (代表加锁状态) normal状态(正常状态)
- thread:54(线程id) epoch:2 age(4) biased_lock:1(代表是偏向锁)|01(代表加锁状态) biased(偏向锁)
- ptr_to_heavyweight_monitor(30,指向monitor的指针) |10(代表加了重量级 heavyweight lock(重量级锁定)
- ptr_to_lock_record:30 (30位代表锁记录的地址) |00(代表加轻锁) LightWeight Lock(轻量级锁定)
monitor是操作系统提供的,内部结构
-
waitset
-
entrylist(等待队列)
-
owner(monitor拥有者)
`具体步骤1是字节码中的monitorenter操作指令 步骤4是字节码中monitorexit指令
重量级锁的加锁流程:
1. thread0执行synchronize代码的时候,synchronized(obj)的obj对象的markword中ptr_to_heavyweight_monitor会指向一个monitor对象,monitor的owner是thread0
2. thread1执行到synchronized代码时,发现obj的markword指向了一个monitor并且owner有人了,这时会进入entrylist进行blocked
3. thread2也一样
4. thread0执行完同步代码退出synchronized,把obj markword里的数据还原比如hashcode age啥的,这些数据是存在monitor对象中的,然后唤醒entrylist的thread1和thread2的blocked线程,两个线程去抢owner
5. 如果出现异常,jvm会自动释放锁 执行第4步
`
因为获取monitor是系统指令,需要从用户态转为内核态,出现上下文切换啥的,比较耗费资源,所以java1.6版本对synchronized进行了优化
-
轻量级锁/使用场景是一个对象虽然有多个线程访问,但是不出现竞争,这时使用轻量级锁来优化
加锁流程:- 创建锁记录(lock record)对象(内部存储锁定对象的mark word,还有一个对象指针记录)
- 让锁记录中的object reference指向锁对象,将锁对象的mark word值存入锁记录(这时锁记录记录的是hashcode age 01无锁状态,obj锁对象对象头中存储的是锁记录地址和状态00 锁添加成功
- 如果obj锁对象中的状态已经是00锁记录存储的是其他线程这时加锁失败
cas替换失败了有两种情况
1.如果其他线程已经持有了该object的轻量级锁,这时出现了竞争,进行锁膨胀
2.如果是线程自己执行了锁重入,添加一条lock record在锁持有线程的栈帧中 指向obj锁对象 并进行cas操作(一定失败),作为重入的计数
释放锁流程:
- 退出同步代码块时,如果有锁记录为null的情况表示有重入,重置锁记录表示重入计数减一
- 当锁记录不为null,这时使用cas操作恢复markword给obj锁对象的对象头
两种情况: - 恢复成功 解锁成功
- 失败说明轻量级锁进行了锁膨胀,这时需要执行重量级锁的解锁流程
锁膨胀:
轻量级锁出现了竞争(在尝试加轻量级锁的过程中,其他线程为此对象已经加上了轻量级锁),这时需要进行锁膨胀,升级为重量级锁
thread0持有着轻量级锁,thread1进来加轻量级锁,出现竞争这时thread1加锁失败进行锁膨胀,
1. thread1为obj对象申请monitor对象,并将obj锁对象的mark word中指向monitor,thread1自己进入entrylist进行blocked
2. 当thread0执行完同步代码块,解锁的时候发现锁的标识变了,mark word也不是指向栈帧中的锁记录了,这时执行重量级锁的解锁流程
自旋优化(适用于多核处理器)
重量级锁出现竞争的时候,使用自旋来优化
偏向锁(优先加偏向锁)
轻量级锁在加锁成功后,锁重入的时候会再执行一次cas的操作(虽然必定失败),这样会浪费资源
偏向锁的加锁流程:
第一次使用cas操作将线程的id存入obj对象头的markword,之后发现这个id是自己就说明没有竞争,不用重新cas操作,只要不发生竞争,这个锁就归这个线程所有
如果调用对象的hashcode,会撤销obj锁对象的偏向状态
当申请obj对象锁的线程是不偏向的那个线程的时候(这时没有竞争),会升级为轻量级锁
批量重偏向
当偏向锁撤销超过20次,重偏向给加锁的线程
批量撤销
当偏向锁撤销超过40次,整个类的所有对象都变为不可偏向的
锁消除
jit及时编译器在运行时会优化掉