synchronized(2)-偏向锁

一、偏向锁简介

Java偏向锁是 Java6 引⼊的⼀项多线程优化。顾名思义,它会 偏向于第⼀个访问锁对象的线 ,如果同步锁只有⼀个线程访问,则线程是不需要触发同步的,这种情况下, 就会给该线 程加⼀个偏向锁 ;如果在运⾏过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁升级到 轻量级锁 ,然后再唤醒原持有偏向锁的线程。

二、偏向锁的场景

通过上述简介可以看出,偏向锁的场景非常苛刻只有在同步块只有一个线程不断访问的情况下才会展现出它的优势,但是在我们编码的大部分场景下都不能满足该要求,所以在Java15中废弃了偏向锁。

三、偏向锁默认启动设置

JDK 1.6 之后默认是开启偏向锁的,为什么初始化的代码是⽆锁状态,进⼊同步块产⽣竞争就绕过偏向锁直接变成轻量级锁了呢?

虽然默认开启了偏向锁,但是开启有延迟,⼤概 4s( 只有项目初始化4s之后新建的对象才会进入可偏向状态 。原因是 JVM 内部的代码有很多地⽅⽤到了synchronized( 不符合偏向锁的场景,不会带来性能的提升,还会造成偏向锁到轻量级锁的升级 ),如果直接开启偏向,产⽣竞争就要有锁升级,会带来额外的性能损耗,所以就有了延迟策略。我们可以通过参数 -XX:BiasedLockingStartupDelay=0 将延迟改为0,但是不建议这么做。

在上述图中描述了不同级别锁之间的升级以及解锁的全部过程,其中锁只能由偏向状态转为非偏向状态,他是单向且不可逆的,锁的状态可细分为五种状态:

  1. 可偏向状态:jvm开启了偏向锁,该对象是jvm启动4s之后创建的对象,并且还没有线程访问过该同步块。
  2. 已偏向状态:jvm开启了偏向锁,该对象是jvm启动4s之后创建的对象,只有一个线程访问了该同步块。
  3. 无锁状态:无锁状态分为两种。第一种是该对象是jvm启动4s之前创建的对象,则直接是无锁状态。第二种是该对象原本处于已偏向状态,并且原持有偏向锁的线程当前没有访问同步块,此时另一个线程访问同步块,会先进入无锁状态(也成为撤销偏向,下文会介绍)。
  4. 轻量级锁:该对象处于无锁状态,此时有线程竞争会升级为轻量级锁。
  5. 重量级锁:当对象处于已偏向或者轻量级锁状态时,并且仍持有锁对象(同步块未执行完毕),此时要计算对象的hashcode值,会直接膨胀为重量级锁。

四、不同场景下锁状态转变的代码示例

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.TimeUnit;

/**
 * @author: zhouzhenghu
 * @description: 描述synchronized不同场景下锁对象状态的转变
 * @date: 2024/6/27 21:05
 * @version: 1.0
 */
@Slf4j
public class LockTest {

    /**
     * @param :
     * @return void
     * @author ZHOUZH
     * @description 偏向4s延迟之前创建锁对象
     *  【前提】:偏向延迟4s内创建的对象
     *  【变化】:无锁——>轻量级锁
     * @date 2024/6/30 15:15
     */
    @Test
    public void test1() {
        Object obj = new Object();
        logPrint(obj, "【1】未进入同步块,MarkWord为:"); // non-biasable-无锁:十六进制最后一位的1=0001

        synchronized (obj) {
            logPrint(obj, "【2】进入同步块,MarkWord为:"); // thin lock-轻量级锁:十六进制最后一位的0=0000
        }
    }

    /**
     * @param :
     * @return void
     * @author ZHOUZH
     * @description 超出偏向延迟
     *  【前提】:偏向延迟4s后创建的对象
     *  【变化】:可偏向状态——>已偏向状态——>已偏向状态(未持有锁)
     * @date 2024/6/30 15:21
     */
    @Test
    @SneakyThrows
    public void test2() {
        TimeUnit.SECONDS.sleep(5);// 睡眠 5s

        Object obj = new Object();
        logPrint(obj, "【1】未进入同步块,MarkWord为:"); // biasable-可偏向状态(101 + 没有具体线程):十六进制最后一位的5=0101

        synchronized (obj) {
            logPrint(obj, "【2】进入同步块,MarkWord为:"); // biased-已偏向状态(101 + 有具体线程):在obj对象的MarkWord中已经存在线程id了
        }
        logPrint(obj, "【3】跳出同步块,MarkWord为:"); // biased-跳出同步块,还是已偏向状态,即:线程id依然存在
    }

    /**
     * @param :
     * @return void
     * @author ZHOUZH
     * @description 锁升级:偏向锁升级为轻量级锁
     *  【前提】:偏向延迟4s后创建的对象
     *  【变化】:可偏向状态——>已偏向状态——>已偏向状态(未持有锁)——>轻量级锁——>无锁——>轻量级锁——>无锁
     * @date 2024/6/30 15:28
     */
    @Test
    @SneakyThrows
    public void test3() {
        TimeUnit.SECONDS.sleep(5); // 睡眠 5s

        Object obj = new Object();
        logPrint(obj, "【1】未进入同步块,MarkWord为:"); // biasable-可偏向状态(101 + 没有具体线程):十六进制最后一位的5=0101

        synchronized (obj){
            logPrint(obj, "【2】进入同步块,MarkWord为:"); // biased-已偏向状态(101 + 有具体线程):在obj对象的MarkWord中已经存在线程id了
        }
        logPrint(obj, "【3】跳出同步块,MarkWord为:"); // biased-跳出同步块,还是已偏向状态,即:线程id依然存在

        // 下面这一步有一个隐式的转换,他是先由已偏向状态(未持有锁)转为无锁,再有无锁转为轻量级锁
        Thread t2 = new Thread(() -> {
            synchronized (obj) {
                logPrint(obj, "【4】新线程获取锁,MarkWord为:"); // thin lock-轻量级锁:十六进制最后一位的0=0000
            }
        });
        t2.start();
        t2.join();
        logPrint(obj, "【5】跳出同步块,MarkWord为:"); // non-biasable-无锁(001 + 没有具体线程):十六进制最后一位的1=0001

        synchronized (obj){
            logPrint(obj, "【6】主线程再次进入同步块,MarkWord 为:"); // thin lock-轻量级锁:十六进制最后一位的0=0000
        }

        logPrint(obj, "【7】跳出同步块,MarkWord为:"); // non-biasable-无锁(001 + 没有具体线程):十六进制最后一位的1=0001
    }


    /**
     * @param :
     * @return void
     * @author ZHOUZH
     * @description 【hashcode场景1】:对象处于可偏向状态,没有线程竞争,计算hashcode
     *  【前提】:偏向延迟4s后创建的对象
     *  【变化】:可偏向状态——>无锁——>轻量级锁
     *  【说明】:对象计算hashcode需要地方存储计算的值,偏向状态的对象是没有地方存储hashcode的,所以要转为有地方存储hashcode的无锁状态
     * @date 2024/6/30 15:30
     */
    @Test
    @SneakyThrows
    public void test4() {
        TimeUnit.SECONDS.sleep(5); // 睡眠 5s

        Object obj = new Object();
        logPrint(obj, "【1】未生成 hashcode,MarkWord 为:"); // biasable-可偏向状态(101 + 没有具体线程):十六进制最后一位的5=0101

        obj.hashCode();
        logPrint(obj, "【2】已生成 hashcode,MarkWord 为:"); // 无锁(001 + hashcode值):十六进制最后一位的1=0001

        synchronized (obj){
            logPrint(obj, "【3】进入同步块,MarkWord 为:"); // thin lock-轻量级锁:十六进制最后一位的0=0000
        }
    }

    /**
     * @param :
     * @return void
     * @author ZHOUZH
     * @description 【hashcode场景2】:对象处于已偏向状态,但是没有线程竞争,计算hashcode
     *  【前提】:偏向延迟4s后创建的对象
     *  【变化】:可偏向状态——>已偏向状态——>无锁——>轻量级锁
     *  【说明】:对象计算hashcode需要地方存储计算的值,已偏向状态的对象是没有地方存储hashcode的,所以要转为有地方存储hashcode的无锁状态
     * @date 2024/6/30 15:33
     */
    @Test
    @SneakyThrows
    public void test5() {
        TimeUnit.SECONDS.sleep(5); // 睡眠 5s

        Object obj = new Object();
        logPrint(obj, "【1】未生成 hashcode,MarkWord 为:"); // biasable-可偏向状态(101 + 没有具体线程):十六进制最后一位的5=0101

        synchronized (obj) {
            logPrint(obj, "【2】进入同步块,MarkWord 为:"); // biased-已偏向状态(101 + 有具体线程):在obj对象的MarkWord中已经存在线程id了
        }

        obj.hashCode();
        logPrint(obj, "【3】生成 hashcode"); // 无锁状态

        synchronized (obj) {
            logPrint(obj, "【4】同一线程再次进入同步块,MarkWord 为:"); // thin lock-轻量级锁:十六进制最后一位的8=1000
        }
    }

    /**
     * @param :
     * @return void
     * @author ZHOUZH
     * @description 【hashcode场景3】:对象处于已偏向状态,并且持有锁,计算hashcode
     *  【前提】:偏向延迟4s后创建的对象
     *  【变化】:可偏向状态——>已偏向状态——>重量级锁
     *  【说明】:对象计算hashcode需要地方存储计算的值,已偏向状态的对象是没有地方存储hashcode的,
     *  又因为此时正持有偏向锁,所以要转为有地方存储hashcode的重量级锁,
     *  重量级锁指针所指向的对象(ObjectMonitor类)中有地方可以记录无锁状态下(标志位为“01”)下的markword,
     *  其中自然可以存储hashcode值
     * @date 2024/6/30 15:35
     */
    @Test
    @SneakyThrows
    public void test6() {
        TimeUnit.SECONDS.sleep(5); // 睡眠 5s

        Object obj = new Object();
        logPrint(obj, "【1】未生成 hashcode,MarkWord 为:"); // biasable-可偏向状态(101 + 没有具体线程):十六进制最后一位的5=0101

        synchronized (obj){
            logPrint(obj,"【2】进入同步块,MarkWord 为:"); // biased-已偏向状态(101 + 有具体线程):在obj对象的MarkWord中已经存在线程id了

            obj.hashCode();

            logPrint(obj,"【3】已偏向状态下,生成 hashcode,MarkWord 为:"); // fat lock-重量级锁:十六进制最后一位的a=1010
        }
    }


    /**
     * @param :
     * @return void
     * @author ZHOUZH
     * @description 【hashcode场景4】假如对象处于轻量级锁的状态,并且持有锁,计算hashcode
     *  【前提】:偏向延迟4s内创建的对象
     *  【变化】:无锁——>轻量级锁——>重量级锁
     *  【结论】:如果对象处在轻量级锁的状态,生成 hashcode 后,就会直接升级成重量级锁
     * @date 2024/6/30 15:45
     */
    @Test
    @SneakyThrows
    public void test7() {
        Object obj = new Object();
        logPrint(obj, "【1】未进入同步块,MarkWord为:"); // non-biasable-无锁:十六进制最后一位的1=0001

        synchronized(obj) {
            logPrint(obj, "【2】进入同步块,MarkWord为:"); // thin lock-轻量级锁:十六进制最后一位的0=0000

            obj.hashCode();

            logPrint(obj,"【3】已偏向状态下,生成 hashcode,MarkWord 为:"); // fat lock-重量级锁:十六进制最后一位的a=1010
        }
    }

    public void logPrint(Object obj, String info) {
        log.info(info);
        log.info(ClassLayout.parseInstance(obj).toPrintable());
    }

}

上图中更加详细地描述了代码示例中不同case下状态流转的过程。并且代码方法备注中都有详细描述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值