synchronized锁的升级流程


前言

在了解锁的升级流程之间,我们首先要理解什么是锁?

锁是一种用于控制对共享资源访问的机制,它可以确保在某个时刻只有一个线程或进程可以访问被锁定的资源,从而保证数据的一致性和安全性。在计算机科学中,锁常用于并发编程,以解决多线程或多个进程并发访问共享资源时可能出现的数据竞争和不一致性问题。

那么,什么是锁的升级呢?

锁升级是指将多个细粒度锁转换为粗粒度锁的过程,叫做锁升级,需要注意的锁只会升级不会降级,锁一旦升级到更高级别,就不会再回到低级别。

为什么要有锁升级流程?
在最开始的时候,其实就是无锁直接到重量级锁,但是重量级锁需要向内核申请额外的锁资源,这就涉及到用户态和内核态的转换,比较浪费资源,而且大多数情况下,其实还是一个线程去争抢锁,完全不需要重量级锁。所以锁的升级可以提高获得锁和释放锁的效率。

一、锁的状态

在jdk1.5版本(包含)之前,锁的状态只有两种状态:“无锁状态”和“重量级锁状态”,只要有线程访问共享资源对象,则锁直接成为重量级锁,jdk1.6版本后,对synchronized锁进行了优化,新加了“偏向锁”和“轻量级锁”,用来减少上下文的切换以提高性能,所以锁就有了4种状态。

1. 无锁

  • 这是对象未被任何线程锁定时的初始状态。此时没有线程持有锁,所有访问同步代码块的线程都是无锁竞争状态。

2. 偏向锁

  • 当一个线程首次获得对象锁时,JVM会将锁设置为偏向锁,并将锁对象的Mark Word(对象头的一部分,用于存储锁状态及线程信息)中的线程ID设置为当前线程的ID。
  • 后续当这个线程再次请求相同的锁时,只需检查Mark Word中的线程ID是否与当前线程ID一致。如果一致,说明还是原来的线程持有锁,可以直接进入同步代码块,无需进行额外的同步操作。
  • 偏向锁的作用:偏向锁适用于大部分时间只有一个线程访问同步资源的场景,减少了轻量级锁中CAS操作的开销。

3. 轻量级锁

  • 当有第二个线程尝试获取已被偏向锁锁定的对象时,偏向锁失效,JVM会尝试升级为轻量级锁。
  • 线程会在当前线程栈中创建一个锁记录(Lock Record),并将锁对象的Mark Word替换为指向锁记录的指针,同时在锁记录中存储当前线程的ID和一个指向原Mark Word副本的指针。
  • 使用CAS操作尝试将锁对象的Mark Word设置为指向锁记录的指针。如果成功,线程获得轻量级锁并执行同步代码;如果失败(即有其他线程已持有锁),则进入下一步骤。
  • 轻量级锁的作用:轻量级锁适用于短暂的、低竞争的同步场景,通过自旋等待和CAS操作避免了线程切换的开销。

4. 重量级锁

  • 当自旋尝试失败或自旋超过一定阈值,或者系统检测到多个线程长期竞争同一锁时,轻量级锁会升级为重量级锁。
  • 重量级锁通常涉及操作系统级别的互斥量(Mutex),线程在无法获得锁时会被挂起,不再消耗CPU资源,直到持有锁的线程释放锁后,操作系统再唤醒等待队列中的下一个线程。
  • 重量级锁提供了严格的互斥保证,适用于高竞争或锁占用时间较长的场景,虽然开销较大,但能有效防止过多线程同时阻塞在自旋状态。

重量级锁是操作系统层面的,不但需要依靠操作系统级别的互斥量保证某一时刻只能由一个线程访问到被锁定的资源,其他线程争夺锁失败而阻塞时,操作系统也负责将该线程挂起并将其从CPU上移除。所以重量级锁的性能最低。

二、Markword

Mark Word是对象头中最重要的部分,它是一个特殊的字段,用于存储对象的元数据信息,包括锁状态和线程信息。在64位JVM中,Markword占用64位。
在这里插入图片描述

这里可以看到对象头信息中用四位来表示对象的分代年龄。这就是为什么默认对象年龄达到15就升级为老年代。

三、锁升级流程

当一个共享资源首次被某个线程访问时,锁就会从无锁状态升级到偏向锁状态,偏向锁会在Markword的偏向线程ID里存储当前线程的操作系统线程ID,偏向锁标识位是1,锁标识位是01。此后如果当前线程再次进入临界区域时,只比较这个偏向线程ID即可,这种情况是在只有一个线程访问的情况下,不再需要操作系统的重量级锁来切换上下文,提供程序的访问效率。

当第二个线程尝试获取偏向锁失败时,偏向锁会升级为轻量级锁,此时,JVM会使用CAS自旋操作来尝试获取锁,如果成功则进入临界区域,否则升级为重量级锁。

轻量级锁是在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,尝试拷贝锁对象头的Markword到栈帧的Lock Record,若拷贝成功,JVM将使用CAS操作尝试将对象头的Markword更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象头的Markword。若拷贝失败,若当前只有一个等待线程,则可通过自旋继续尝试, 当自旋超过一定的次数,或者一个线程在持有锁,一个线程在自旋,又有第三个线程来访问时,轻量级锁就会膨胀为重量级锁。

概述:
一开始是无锁状态,然后当一个线程首次获得对象锁时,JVM会设置为偏向锁,然后其他线程也来尝试获取对象,发现对象被加上了偏向锁,会将偏向锁进行撤销,此时对象是无锁状态,第一个来到的线程会将对象头的Mark Word替换为指向锁记录的指针,并将锁记录中的Owner字段设置为当前线程,此时锁是轻量级锁。后面到来的线程进行CAS操作进行修改。如果自旋一定次数任未操作成功,那么锁就会升级为重量级锁。

锁升级细化流程:
在这里插入图片描述

四、代码演示

  1. 首先引入依赖用来显示对象的内存信息
	<dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.17</version>
    </dependency>
  1. 代码如下:(需要加上jvm配置:-XX:BiasedLockingStartupDelay=0)
package com;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author:汐泠
 */
public class test {
    static Object obj = new Object();
    //指定任务
    static class LockThread implements Runnable{
        @Override
        public void run() {
            synchronized (obj){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {

        //只有一个线程去抢占锁
        new Thread(new LockThread()).start();
        System.out.println("===========偏向锁===========");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());

        //两个线程抢占锁,升级为轻量级锁
        new Thread(new LockThread()).start();
        Thread.sleep(50);
        System.out.println("===========轻量级锁===========");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());

        //多个线程抢占,一直在自旋,升级为重量级锁
        for (int i = 0; i < 20; i++) {
            new Thread(new LockThread()).start();
        }
        System.out.println("===========重量级锁===========");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());

    }



}


  1. 输出信息如下:
    在这里插入图片描述
    在这里插入图片描述

总结

synchronized关键字是Java中常用的线程同步机制,其具备锁升级的特性,可以根据竞争的程度和锁的状态进行自动切换。锁升级通过无锁、偏向锁、轻量级锁和重量级锁四种状态的转换,以提高并发性能。在实际开发中,我们应该了解锁升级的原理,并根据具体场景进行合理的锁设计和优化,以实现高效且安全的多线程编程。

随着jdk版本的升级,JVM底层的实现持续优化,版本的不同伴随着参数使用及默认配置的不同,但总之JVM层对synchronized的优化效率越来越高,所以不应该再把synchronized同步当重量级锁来看。

其实本文介绍了锁升级的主要过程,关于synchronized还有锁消除、锁粗化的优化手段,使得synchronized性能在某些场景应用下,可能会比JUC包底下的Lock相关锁效率更高。
另外synchronized锁原理、优化、使用远不止本文说的这么多,感兴趣的可进一步探索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值