多线程-锁升级过程

一、概述

Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。因此锁一共有4种状态,从低到高依次是:无锁、偏向锁、轻量级锁、重量级锁

二、概念

下面依次介绍四种锁:
1、偏向锁

Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下 Mark Word中偏向锁的标识是否设置成 1(表示当前是偏向锁),如果没有设置,则使用 CAS 竞争锁,如果设置了,则尝试使用 CAS 将对象头的偏向锁指向当前线程。

2、轻量级锁

轻量级锁加锁:线程在执行同步块之前, JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用 CAS 将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为”10”,Mark Word中存储的就是指向重量级(互斥量)的指针。
轻量级锁解锁:轻量级解锁时,会使用原子的 CAS 操作来将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

3、重量级锁

内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。
如果一个锁为重量级锁,当一个线程拥有这把锁的时候,其它线程处于阻塞状态。

三、实战

通过 Java 代码来分析

在此之前,首先粗略认识一下 Java 对象的构成,Java对象头由4部分构成

1 — Java对象头中MarkWord信息(64位虚拟机的MarkWord为64bit)
2 — Java对象头中元数据指针 (User对象没有数组,所以没有数组的长度信息)
3 — 实例数据,以及数据的初始值信息
4 — 填充长度,保证对象的长度是8的整数倍(64位虚拟机),方便寻址,和操作系统内部有关

我们重点关注的是 Java 对象头中 MarkWord 的变化,有关锁的信息都存储在里面,这里直接上图
可以看出,不同的锁对应这不同的标志位
多线程-锁升级过程_第1张图片

1、第一步,导入依赖
<!--可以查看对象相关的信息-->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>
2、创建一个 User实体类,后面用来实例化得到对象
public class User {
    private int id;
    private String name;
	
	// 这里省略构造方法和set、get方法
	..........
}
3、实例化,查看无锁状态
public class LockUpdate {
    public static void main(String[] args) throws Exception {
        User userTemp = new User();
        System.out.println("无状态001: " + ClassLayout.parseInstance(userTemp).toPrintable());
    }
}

多线程-锁升级过程_第2张图片
运行结果可以看出,此时为无锁 001 状态

4、无锁状态变为偏向锁

注意:JVM 默认延时4s开启偏向锁,可通过 -XX:BiasedLockingStartupDelay=0 取消延时。如果不要偏向锁,可以通过**-XX:-UserBiasedLocking=false**来设置

public class LockUpdate {
    public static void main(String[] args) throws Exception {
        User userTemp = new User();
        System.out.println("无状态001: " + ClassLayout.parseInstance(userTemp).toPrintable());

        /* JVM 默认延时4s开启偏向锁,可通过 -XX:BiasedLockingStartupDelay=0 取消延时
        如果不要偏向锁,可以通过-XX:-UserBiasedLocking=false来设置
         */
        Thread.sleep(5000);

        User user = new User();
        System.out.println("偏向锁101: " + ClassLayout.parseInstance(user).toPrintable());
    }
}

多线程-锁升级过程_第3张图片
运行结果可以看出,经过5秒之后,锁变成了轻量级锁,Java 虚拟机会默认延时开启偏向锁,但是注意,此时还没有在对象头中存储线程的 ID

5、对象头中存储锁偏向的线程 ID

对这个对象重复加锁

for(int i = 0; i < 2; i++){
    synchronized (user){
        System.out.println("偏向锁101 (带线程id): " + ClassLayout.parseInstance(user).toPrintable());
    }
    /* 释放偏向锁,但对象中的线程id不会被改变,因为是偏向的
       下次这个对象再次获取锁的时候,只需要判断这个对象头的MarkWord中是否存储着指向该线程的线程ID
     */
    System.out.println("释放偏向锁101 (带线程id): " + ClassLayout.parseInstance(user).toPrintable());
}

多线程-锁升级过程_第4张图片
如上图,可以发现对象头中的线程 ID 被改变了

多线程-锁升级过程_第5张图片
当线程释放锁(在synchronized代码块外面),对象头里面的线程 ID 不会变化,直到出现另外的线程。
下一步我们开启另外的线程,模拟有线程竞争的情况。

6、偏向锁升级为轻量级锁
/* 
这里开启另外的线程,模拟有线程竞争的情况
*/

new Thread(()->{
    synchronized (user){
        System.out.println("轻量级锁00: " + ClassLayout.parseInstance(user).toPrintable());
    }
}).start();

多线程-锁升级过程_第6张图片
可以看出偏向锁101 变成了轻量级锁00

7、轻量级锁变为重量级锁

通过在上面的代码块中加入休眠,因为在sleep的过程中,对象不会释放锁,因此此时如果再开启线程,又会出现线程竞争的情况。轻量级锁变为重量级锁。

new Thread(()->{
    synchronized (user){
        System.out.println("轻量级锁00: " + ClassLayout.parseInstance(user).toPrintable());
        try {
            //System.out.println("睡眠4秒钟======================");
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("轻量级-->重量级10: " + ClassLayout.parseInstance(user).toPrintable());
    }
}).start();


/*
 * 在上一个线程休眠的时候,没有释放锁,此时又有多个线程参与竞争,会变成重量级锁
 * 一旦升级成重量级锁,就不会再变成轻量级状态
 */
Thread.sleep(1000);
new Thread(()->{
    synchronized (user){
        System.out.println("重量级锁10: " + ClassLayout.parseInstance(user).toPrintable());
    }
}).start();

多线程-锁升级过程_第7张图片
锁升级为了重量级锁!!!升级为重量级锁之后,指针指向 monitor。

你可能感兴趣的:(Java学习笔记,java,多线程)