Synchronized 精讲 (5)

引入轻量级锁的主要目的是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁,其步骤如下:

在线程进入同步块时,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的复制

拷贝对象头中的Mark Word复制到锁记录(Lock Record)中。

拷贝成功后,虚拟机将使用CAS操作尝试将对象Mark Word中的Lock Word更新为指向当前线程Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。

如果这个更新动作成功了,那么当前线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态

如果这个更新操作失败了,虚拟机首先会检查对象Mark Word中的Lock Word是否指向当前线程的栈帧,如果是,就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,进入自旋执行(3),若自旋结束时仍未获得锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。

图片

轻量级锁的释放也是通过CAS操作来进行的,主要步骤如下

通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word

如果替换成功,整个同步过程就完成了,恢复到无锁状态(01)

如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程

问题:

为什么升级为轻量锁时要把对象头里的Mark Word复制到线程栈的锁记录中呢?

因为在申请对象锁时需要以该值作为CAS的比较条件,同时在升级到重量级锁时,能通过这个比较判定是否在持有锁的过程中此锁被其他线程申请过,如果被其他线程申请了,则在释放锁的时候要唤醒被挂起的线程。

为什么会尝试CAS不成功以及什么情况下会不成功?

CAS本身是不带锁机制的,其是通过比较来操作得。假设如下场景:线程A和线程B都在对象头里的锁标识为无锁状态进入,那么如线程A先更新对象头为其锁记录指针成功之后,线程B再用CAS去更新,就会发现此时的对象头已经不是其操作前的对象了,所以CAS会失败。也就是说,只有两个线程并发申请锁的时候会发生CAS失败。

此时线程B进行CAS自旋,等待对象头的锁标识重新变回无锁状态或对象头内容等于对象,这也就意味着线程A执行结束,此时线程B的CAS操作终于成功了,于是线程B获得了锁以及执行同步代码的权限。如果线程A的执行时间较长,线程B经过若干次CAS时钟没有成功,则锁膨胀为重量级锁,即线程B被挂起阻塞、等待重新调度

5.8 重量级锁

Synchronized是通过对象内部的一个叫做监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,性能消耗特别严重。 因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为 “重量级锁”。

6. 缺点

效率低

锁的释放情况少

试图获取锁时不能设定超时

不能中断一个正在试图获得锁的线程

不够灵活

加锁和释放锁的时候单一,每个锁仅有一个单一条件

不知道是否成功获取锁

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjdfy.html