JAVA多线程学习-------monitor所实现原理

1.Java对象头

Java对象头详解

2.monitor锁原理

Monitor 被翻译为监视器或管程 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
Monitor 结构如下!
在这里插入图片描述
同步监视器对象obj首先会向操作系统申请获取一个monitor,获取到之后会把obj对象头中的mark word中的lock字段由01改为10,并且把前30位改为monitor地址。

刚开始 Monitor 中 Owner 为 null 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一 个 Owner 在 Thread-2 上锁的过程中,如果 Thread-1,Thread-3, 也来执行 synchronized(obj),就会进入 EntryList BLOCKED Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的 图中 WaitSet 中的 Thread-0,Thread-4 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲 wait-notify 时会分析
注意:
synchronized 必须是进入同一个对象的 monitor 才有上述的效果
不加 synchronized 的对象不会关联监视器,不遵从以上规则

3.synchronized实现原理

1. 轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个方法同步块,利用同一个对象加锁

static final Object obj = new Object();
public static  void method1(){
	synchronized(obj){
		method2();
	}
}
public static void method2(){
	synchronized(obj){
		//同步代码块
	}
}

首先会进入轻量级锁流程:

  • 执行到method1()时,会在当前线程栈中创建一个栈帧(method1栈帧)。执行到synchronized同步代码块时,method1栈帧中的Lock Record锁记录中的地址和标志位00会和同步监视器对象obj的mark word进行CAS操作(锁记录地址和markword交换操作),当obj的markword的后两位lock位位01时,表示当前锁没有被其他线程锁占有。

  • 在这里插入图片描述

  • 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁
    在这里插入图片描述

  • CAS操作失败有两种情况
    ① 当前线程已经拥有了当前锁。就会进入synchronized锁重入,添加一条lock reord锁记录作为重入计数。
    ②其他线程已经获取了当前锁。会进入锁膨胀(下节讲述)。

  • 当发生锁重入时(例如执行到method2的synchronized时),会把当前栈帧的lockRecord地址赋值为null,objectReference为obj对象。
    在这里插入图片描述

  • 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重 入计数减一

在这里插入图片描述

  • 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象 头 成功,则解锁成功 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

在这里插入图片描述

2.锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有 竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

static Object obj = new Object();
public static void method1() {
    synchronized( obj ) {
            // 同步块    
            } 
 }
  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

在这里插入图片描述

  • 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址 然后自己进入 Monitor 的 EntryList BLOCKED

在这里插入图片描述

  • 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

3.自旋优化

如果线程直接进入EntryList堵塞状态,如果刚好锁owner释放锁,堵塞线程会被唤醒,成为owner,这些过程都伴随线程上下文的切换,浪费时间。
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步 块,释放了锁),这时当前线程就可以避免阻塞。
自旋重试成功的情况
在这里插入图片描述
自旋重试失败的情况
在这里插入图片描述
自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会 高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
Java 7 之后不能控制是否开启自旋功能

偏向锁–>轻量级锁(自旋锁)–>重量级锁

如果开启了偏向锁,第一个线程获取锁时,首先会进行偏向锁,会在当前线程中的栈帧中创建lockRecord锁记录用来复制锁对象的markWord字段,锁对象的markWord就会把偏向状态位设置为1,表示锁位偏向锁。把当前线程的id保存到锁对象的markWord。如果再有线程需要获取锁,首先判断自己的线程id和锁MarkWord中保存的线程id是否一样,如果一样,直接进入同步代码。如果不一样,会判断偏向的线程是否存活,如果线程没有存活,就会重新偏向新的线程。如果偏向的线程存活,就会从偏向锁膨胀为轻量级锁。偏向线程会暂停执行,会把锁对象的偏向锁标志位从1变为0,锁标志位从01变为00,然后把 markwordc利用CAS操作保存到偏向线程的栈帧的lockRecord中,并且把own指向锁对象,然后锁对象的markWord前29bit保存lockRecord的地址。新来的线程会进行自旋操作。如果此时又来了线程,锁记录标志位为00轻量级锁,就会进入重量级加锁流程,会创建一个Monitor对象,锁对象的后三位会从000变为010,前29bit会指向monitor对象。monitor的owner指向那个线程对象。新来的线程会进入EntryList阻塞队列中。

推荐—>趣谈锁升级:我是厕所所长(一)—马士兵
推荐—>趣谈锁升级:我是厕所所长(二)—马士兵

注意:
1.synchronized锁的是对象,而不是代码。
2.在如果同步代码块执行时间短并且并发线程数较少,推荐使用自旋锁(在cpu中执行,没有内核态向用户态的转换,如原子锁等)。
如果同步代码块执行时间长或者并发线程数较多,推荐使用重量级锁(synchronized)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值