Monitor原理
Monitor
-
java对象头
- 普通对象【32bit的jvm】:
- Mark word【32 bits】:对象信息;
- Klass Word【32 bits】:指向Klass对象【Class对象】;
- 数组对象:
- Mark word【32 bits】
- Klass Word【32bits】
- arraylength【32bits】
- Mark word的结构
- normal state:
- hashcode【25bits】+age【3bits,年代区别】+biased_lock【1bit】+ 锁标志【2bits】;
- Biased state:
- thread【23bits】+epoch【2】+biased_lock【1bit】+ 锁标志【2bits】;
- Lightweit Locked state:
- ptr_to_lock_record【30bits】+ 锁标志【2bits】;
- heavyweight Locked state:
- ptr_to_heavyweight_monitor【30bits】+ 锁标志【2bits】
- marked for GC
- 锁标志【2bits】;
- normal state:
- 普通对象【32bit的jvm】:
-
Monitor【操作系统提供】工作原理:
- 当使用synchronized时,锁的对象会关联一个Monitor对象;
- monitor对象:
- Owner:只能有一个对象;
- EntryList:阻塞【Blocked】的链表,当锁释放的时候,里面的线程会被唤醒;
- WaitSet:
- 字节码流程:
- 遇到synchronized,将lock对象的Mark word置换位Monitor引用,同时将mark word存储到Monitor中;
- 而且底层有:try-catch-finally机制;
-
synchronized的优化:
- 轻量级锁:多个线程错开访问,如果有竞争就会升级;
- synchronized优先轻量级锁;
- 线程内部创建锁记录对象
- 锁记录对象【LockRecord】:
- 【lock record地址,00【轻量级锁标志】】
- 【Object referrence】;
- 将Object referrence指向锁对象,然后使用CAS【乐观锁】的方式替换Object中的mark word的值和【lock record地址,00】
- 锁记录对象【LockRecord】:
- 如果成功了,则表示该线程给对象加锁成功;
- 如果失败了:
- 如果有其他线程已经持有了该对象的轻量级锁,则有竞争,会进入锁膨胀过程;
- 如果是自己持有,那么再添加条Lock record【mark word 00:为null】作为重入的技术;
- 当退出的时候,如果取值有null,则表示有重入,pop出就行;
- 锁记录不为null时,使用CAS将mark word进行恢复;
- 成功:解锁成功;
- 失败:进入重量级解锁流程;
- 线程内部创建锁记录对象
- 锁膨胀:
- 另一个线程加轻量级锁失败后,进入锁膨胀:
- 会给锁对象申请一个monitor锁,并将让Object的mark word指向monitor地址;
- 然乎自己进入monitor的EntryList进行Blocked;
- 当持有轻量级锁退出来后,发现使用CAS给锁对象恢复mark word的时候,会失败;
- 则会根据锁对象的Monitor找到monitor对象,将Ower设置为null;并唤醒EntryList中的线程;
- 自旋优化:
- 进入阻塞会发生上下文切换,降低性能【多核cpu才有意义】;
- 自旋循环几次后重试获取锁【具体自旋次数,比较智能】;
- 偏向锁:
- 轻量级锁在发生锁重入的时候,仍然会要执行CAS操作【进行比较,替换】;
- 在第一次执行CAS操作的时候,将线程ID【操作系统的线程ID】设置到锁对象的的mark word头里面,之后发现线程ID是自己,表示没竞争,就不用重新执行CAS操作;
- 一个对象创建时:
- 如果开启了偏向锁【默认开启】,那么对象创建后,mark word的最后三位是101,这时候他的thread,epoch,age都是0;
- 偏向锁默认是延迟的,不会再程序启动后立即生效;
- 如果没有开启偏向锁,那么mark word的最后三位是001,他的hashcode,age都是0,只有第一次用到时候,才会赋值;
- 当开启偏向锁,主动使用synchronized的偏向锁后,线程ID的值不会消失,会被持续属于这个对象【仅仅第一次的时候】;
- 偏向锁撤销【禁用】:
- 当开启偏向锁后,在调用hashcode,会直接将该对象的偏向锁撤销【因为没位置存hashcode的值了】。
- 当有其他线程再次使用该偏向锁时,会直接将该对象的偏向锁撤销,然后升级为轻量级锁,之后成正常状态;
- 调用wait/notify时,因为这个只有重量级锁才会有,所以会直接升级为重量级锁;
- 批量重偏向【jvm的优化】:
- 如果对下给你被多个线程访问,但是没有竞争,这时偏向线程1的对象仍然有机会重新偏向线程2;
- 【批量重偏向】:当批量偏向线程1的对象给线程2做锁的时候,会产生偏向锁撤销,当阈值超过20次的时候,jvm会自动优化之后的对象重新偏向至线程2【偏向锁撤销是耗费效率的】;
- 【批量撤销】:当撤销偏向阈值超过40次过后,jvm会认为自己有问题,就不应该偏向,所以整个类的对象都会变为不可偏向,包括新建对象【第40个】;
- 锁消除:
- 当代码被反复执行,JIT就会热点代码优化,当并对象并不存在多线程的共享,所以就会把锁消除掉,以提升性能;
-
wait / notify原理:
-
当Owner线程调用wait方法的时候,即可进入monitor的WaitSet集合,变成waiting状态;
-
Monitor的EntryList会在Owner释放锁的时候被唤醒;
-
但是WaitSet则是通过notify 或者 notifyAll方法时,才会被唤醒,并且进入EnttryList进行重新排队;
-
api方法:前提获取了Monitor的锁后,才能调用
-
obj.wait():让进入到监视器的线程到waitSet等待;
-
obj.notify():在object上正在waitSet等待的线程中挑一个唤醒;
-
obj.notifyAll:在object正在waitSet等待的所有线程唤醒;
它们都是Object对象的方法;
-
-
sleep(long n)和wait(long n)的区别:
- sleep是一个Thread的静态方法,wait是一个Object方法;
- wait需要配合synchronized使用;
- sleep在休眠的时候,不会释放锁,wait在等待的时候,会释放锁;
- 这两个都是带时限的等待,属于TIMED_WAITING状态;
- sleep占用cpu效率更低;
-
-
park和unpark(Thread n)方法:
- wait,notifyAll必须配合Monitor使用,而park,unpark不必;
- park和unpark是以线程为单位阻塞和唤醒,notify是随机唤醒,notifyAll是唤醒所有;
- park和unpark,可以先使用unpark,而wait和notify不能,多次调用unpark只是有效一次;
- 每个线程都有一个Paker对象,C代码编写,
- t.join():则当前线程进入waiting状态,当前线程在t1线程对象的监视器上等待;
- 可以调用interrupt()方法打断等待的:
- park();
- sleep();
-
死锁:jconsole工具;
-
活锁:出现两个线程相互改变对象的结束条件,导致谁也无法结束;
-
饥饿:由于线程的优先级太低,得不到cup调度,也不能够结束;

浙公网安备 33010602011771号