【并发编程】synchronized 偏向锁、轻量级锁、重量级锁

关键字 Synchronied

可重入锁、悲观锁、非公平锁、可见性、原子性

几种使用场景

// 1.同步代码块,锁(obj)可以是任意对象或者this        
synchronized (obj){}
// 2.同步方法,锁是this
public synchronized void ticket(){}
// 3.静态同步方法,锁是该类的.class对象
public synchronized static void ticket(){}

不建议使用String作为锁对象,因为常量池的存在,会将两个相同值的String常量指向一个地址,导致结果不符合预期

底层实现

对于同步代码块

public static void main(String[] args) {
    synchronized (new Object()){
        System.out.println();
    }
}

查看字节码

javap -c -s -v -l xxx.class

public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: dup
       8: astore_1
       9: monitorenter
      10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: invokevirtual #4                  // Method java/io/PrintStream.println:()V
      16: aload_1
      17: monitorexit
      18: goto          26
      21: astore_2
      22: aload_1
      23: monitorexit
      24: aload_2
      25: athrow
      26: return

9和17行有一个monitorentermonitorexit来标识同步代码块开始和结束的位置,而23行的monitorexit是为了保证发生异常是能够正确释放锁。其实synchronized就是通过获取Monitor对象监视器,当执行到monitorenter时尝试获取锁,也就是尝试获取对象监视器的持有权,当执行到monitorexit时释放锁。

对于同步方法

public static void main(String[] args) {
    test();
}

public static synchronized void test(){
    System.out.println();
}

查看字节码

  public static synchronized void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: invokevirtual #4                  // Method java/io/PrintStream.println:()V
         6: return
      LineNumberTable:
        line 19: 0
        line 20: 6

可以看到同步方法没有了monitorentermonitorexit取而代之的是ACC_SYNCHRONIZED标识,JVM通过这个标识来表明方法是否是同步方法,从而执行相应的同步调用

不过两者的本质都是对对象监视器 monitor 的获取

锁升级

synchronized的锁是存在java对象头的MarkWord中的,早期实现是重量级锁,1.6以后引用了锁升级的概念加入了偏向锁、轻量级锁。

synchronized锁的四种状态

锁状态是否偏向锁(1bit)锁标识位(2bit)
无锁状态001
偏向锁101
轻量级锁指向栈帧中的锁记录的指针00
重量级锁指向互斥量的指针10

锁可以升级但是不能降级: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

这种策略主要是为了提高获取锁的效率

偏向锁

当一个线程访问同步块并获取锁时,会在对象头的MarkWord和栈帧中的锁记录里存储锁偏向的线程ID,以后在线程进入和退出时就不需要进行CAS操作来加锁和解锁,只需要检查MarkWord中是否存在指向当前线程的偏向锁ID,如果存在则表示已经获取到了锁,如果不存在则看下偏向锁标识是否为1,如果是则通过CAS竞争锁,如果不是则通过CAS将偏向锁指向当前线程。

轻量级锁

如果存在两个线程竞争的情况,则偏向锁会升级成为轻量级锁

首先两个线程都在自己的栈帧中创建用于存储锁记录的空间,并将MarkWord复制到锁记录中。然后A线程尝试通过CAS操作将对象头中的MarkWord替换为指向锁记录的指针,如果成功则代表获取到了锁。这时B线程也尝试通过CAS获取锁,因为MarkWord已经被A线程修改,所以获取失败,为了提高索取锁的效率,B线程会自旋获取锁,当达到一定次数时还是获取失败,这时MarkWord中的记录就会被B线程修改为重量级锁,B线程被挂起,后续线程看到是重量级锁也直接挂起。

解锁时,A线程尝试使用CAS将对象头中的MarkWord改回自己锁记录中的MarkWord,因为对象头中的MarkWord已经被指向为重量级锁了, 所以CAS失败,A线程会释放锁并唤起等待的线程, 进行新一轮的竞争。

比较

优点缺点适用场景
偏向锁加锁解锁无需额外消耗,速度快如果存在锁竞争,则会带来额外的锁撤销的消耗一个线程访问同步块
轻量级锁竞争的线程不会阻塞如果始终得不到锁竞争的线程,自旋会消耗CPU资源追求响应速度,同步块执行速度快
重量级锁线程不使用自旋获取锁,不会消耗CPU资源线程阻塞,响应时间慢追求吞吐量,同步块执行速度较慢

这里有个问题,锁不能降级,那是不是意味着当synchronize升级到重量级锁后,后续就一直是重量级锁了?

其实是不对的,当重量级锁释放后又会变成无锁状态,下次重新走升级路线

验证:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
Object LOCK = new Object();
System.out.println("no lock: " + ClassLayout.parseInstance(LOCK).toPrintable());
IntStream.rangeClosed(1, 3).forEach(item -> {
    new Thread(() -> {
        synchronized (LOCK) {
            System.out.println(Thread.currentThread().getName() + " lock: " + ClassLayout.parseInstance(LOCK).toPrintable());
        }
    }).start();
});

Thread.sleep(1000);
System.out.println("current lock: " + ClassLayout.parseInstance(LOCK).toPrintable());

在这里插入图片描述

如果只有一个线程在获取锁,会发现其实是轻量级锁而不是偏向锁,这是因为1.8 的偏向锁得在 JVM 启动 4 秒后才生效,需要通过jvm参数 -XX:BiasedLockingStartupDelay=0 关闭偏向锁延迟才能观察到偏向锁标识

在这里插入图片描述



参考博客: Synchronized升级成重量级锁之后就下不来了?你错了!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值