锁原理及synchronized锁升级过程


重入锁

image-20210228191931863

synchronized

image-20210228193405292

 

 

synchronized加到代码块上时两种情况

  1. synchronized(this):表示加锁的效果如同加到普通方法上 synchronized(this){} = viod synchronized get(){} ;对象锁:不跨线程保护

  2. synchronized(Test.class):表示加锁的效果如同加到静态方法上 synchronized(this){} = static viod synchronized get(){} ;类锁:跨线程保护

并发:资源共享且互斥

为什么synchronized能够起到阻塞的效果

synchronized(lock) 中被锁定的对象的内存布局

image-20210228194914502

image-20210228195203082

jdk1.6 以后 synchronized的功能进行了优化:对锁的升级规则(无锁->偏向锁(cas)-》轻量级锁(自旋)-》重量级锁(阻塞))

锁升级的规则

假如存在线程1、线程2 两个线程 :

  1. 只有线程1访问-- 》偏向锁 (cas 添加线程id)

  2. 线程1和线程2交替访问 --》轻量级锁-自旋(不是锁,只是轮询cas更换对象的MarkWord)

  3. 两者同时访问-》持锁时间较长(业务处理慢)-》重量级锁

    无锁--》偏向锁

    image-20210228201133245

    偏向锁--》轻量级锁

    image-20210228201042998

wait/notify

线程获取锁 通过monitorenter(指令)成功后获得对象锁,其他线程进入同步队列,wait的线程释放锁进入等待队列

image-20210228201939647

 

 

 

可见性问题

CPU层面的高速缓存带来缓存不一致问题--》可见性问题:

cpu层面解决可见性问题 引入了:

总线锁;缓存锁

缓存一致性协议(x86:MESI):表示缓存行的四种状态(会出现指令重排序:乱序执行)

image-20210228203641843

 

 

总结:cpu层面仍然会存在可见性问题(但是提供内存屏障指令)

读屏障、写屏障、全屏障

对象头mark word

我们可以将上面的注释转成以下的表格

|-----------------------------------------------------------------------------------------------------------------|
|                                             Object Header(128bits)                                              |
|-----------------------------------------------------------------------------------------------------------------|
|                                   Mark Word(64bits)               |  Klass Word(64bits)    |      State         |
|-----------------------------------------------------------------------------------------------------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Nomal         |
|-----------------------------------------------------------------------------------------------------------------|
| thread:54|      epoch:2       |unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Biased        |
|-----------------------------------------------------------------------------------------------------------------|
|                     ptr_to_lock_record:62                 |lock:2 | OOP to metadata object | Lightweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
|                    ptr_to_heavyweight_monitor:62          |lock:2 | OOP to metadata object | Heavyweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
|                                                           |lock:2 | OOP to metadata object |    Marked for GC   |
|-----------------------------------------------------------------------------------------------------------------|

从上面的表格,我们可以看出Java的对象头在对象的不同的状态下会有不同的表现形式,主要有三种状态,无锁状态,加锁状态,GC标记状态。那么就可以理解Java当中的上锁其实可以理解给对象上锁。也就是改变对象头的状态,如果上锁成功则进入同步代码块。但是Java当中的锁又分为很多种,从上图可以看出大体分为偏向锁、轻量锁、重量锁三种锁状态。这三种锁的效率是完全不同、关于效率的分析会在下文分析。我们需要查看对象头,就需要用到借助JOL工具。

首先我们在项目中引入JOL的依赖,具体如下图:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

然后创建A.java

public class A{} 然后创建JOLExample1.java

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

import static java.lang.System.out;

public class JOLExample1 {
   static A a;
public static void main(String[] args) {
   a = new A();
   //打印JVM的详细信息
   out.println(VM.current().details());
   //打印对应的对象头信息
   out.println(ClassLayout.parseInstance(a).toPrintable());
}
}

运行的结果如下

image-20210321140633664

不是说Klass是64bits(8个字节)但是这儿只有4个字节,是因为我们开启了指针压缩,我们可以关闭指针压缩看看,是不是8个字节。我们只需要使用以下的JVM运行参数

-XX:-UseCompressedOops

再次运行刚才的程序,可以看到我们Klass对象是64bits(16个字节),具体如下图

image-20210321141124406

看完了对象的实例数据,我们就来到了今天的重头戏,Java的对象头(在开启JVM指针压缩的情况下是12B),那么这12B存储的是什么?我们可以看下 OpenJDK的官网的解释

首先引用openjdk文档当中对对象头的解释

 

上述引用中提到一个java对象头包含2个word,并且包含了堆对象的布局、类型、GC状态、同步状态和标识哈希码,具体怎么包含的呢?又是哪两个word呢?

Mark word为第一个word根据文档可以知道它里面包含了锁的信息、hashcode、gc信息等等,第二个word是什么呢?

 

klass word 为对象头的第二个word主要指向对象的元数据。

假设我们理解一个对象头主要由上图两个部分组成(数组对象除外,数组对象的对象还包含一个数组长度),由我们的推导出Mark word是8个字节,klass word(开启指针压缩的情况下是4个字节,不开启的时候是8个字节)。我们打印出来的对象头是12个字节,所以其中的8个字节是Mark word,剩下的4个字节是klass word,但是和锁相关的就是Mark word,那么接下来要重点分析Mark word里面信息。

由最开始的64位的表格,我们可以得知在无锁的情况下Markword当中前56bit存的是对象的hashcode,我们来验证一下

修改A.java 的代码如下

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;
public class JOLExample2 {
   public class A {
   //占一个字节的boolean字段
   private boolean flag;
}

新建一个JOLExample2.java具体代码如下

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;
public class JOLExample2 {
public static void main(String[] args) {
   A a = new A();
   //没有计算HashCode之前的对象头
   out.println("before hash");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
   //jvm计算HashCode
   out.println("jvm----------" + Integer.toHexString(a.hashCode()));
   
   //当计算完HashCode之后,我们可以查看对象头的信息变化
   out.println("after hash");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
}
}

运行的结果如下:

在这里插入图片描述

可以看到我们在没有进行hashcode运算的时候,所有的值都是空的。当我们计算完了hashcode,对象头就是有了数据。因为是小端存储,所以你看的值是倒过来的。前25bit没有使用所以都是0,后面31bit存的hashcode,所以第一个字节中八位存储的分别就是分代年龄、偏向锁信息、对象状态,这8bit分别表示的信息如下图所示,这个图会随着对象的状态改变而改变,下图是无锁的状态下

在这里插入图片描述

无锁、偏向锁、轻量锁、重量锁、GC标记( 001,101,00,10,11)

æ— é”çŠ¶æ€ä¸‹çš„å¯¹è±¡å¤´

关于对象状态一共分为五种状态,分别是无锁、偏向锁、轻量锁、重量锁、GC标记

锁状态 锁标识 备注
无锁 001 对象头中使用baised_lock + lock 一共3bit来表示无锁和偏向锁的
偏向锁 101 对象头中使用baised_lock + lock 一共3bit来表示无锁和偏向锁的
轻量锁 00 只用到了lock标识位
重量锁 10 只用到了lock标识位
GC标志 11 只用到了lock标识位

新建一个JOLExample3.java,代码如下:

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample3 {
static A a;

public static void main(String[] args) throws InterruptedException {
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
}

private static void sync() {
   synchronized (a) {
       out.println("我不知道要打印什么");
  }
}
}

}

查看运行结果如下:

在这里插入图片描述

上面这个程序只有一个线程去调用sync方法,应该是偏向锁,但是你会发现输出的结果(第一个字节)依然是00000001和无锁的时候一模一样,其实这是因为虚拟机在启动的时候对于偏向锁有延迟,如果没有偏向锁的延迟的话,虚拟机在启动的时候,可能JVM某个线程调用你的线程,这样就有可能变成了轻量锁或者重量锁,所以要做偏向锁的延迟,那我们怎么看到打印的对象头是偏向锁呢?有两种方式:第一种是加锁之前先让线程睡几秒。第二种加上JVM的运行参数,关闭偏向锁的延迟,具体的命令如下:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

第一种方式:修改JOLExample3.java如下

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample3 {
static A a;

public static void main(String[] args) throws InterruptedException {
   //切记延迟一定要放在对象创建之前,不然是无效的,因为在你对象创建之前,偏向锁的延迟的时间
   //没有给你睡过去,这时候,对象已经创建了,对象头的信息已经生成了。
   Thread.sleep(5000);
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

}

private static void sync() {
   synchronized (a) {
       out.println("lock ing");
       out.println(ClassLayout.parseInstance(a).toPrintable());
  }
}

}

再次运行,查看结果如下:

在这里插入图片描述

可以发现已经变成了00000101,偏向锁,需要注意的after lock,退出同步后依然保持了偏向信息。

第二种方式:利用jvm参数,首先我们先关闭睡眠5秒的,然后运行配置如下:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

再次运行查看结果如下:

在这里插入图片描述

这时候大家会有疑问了,为什么在没有加锁之前是偏向锁,准确的说,应该是叫可偏向的状态,因为它后面没有存线程的ID,当lock ing的时候,后面存储的就是线程的ID(44969989)既然这儿存储是线程的ID,那么HashCode又存储到什么地方去了?是不是计算了HashCode就是不能偏向了?我们来验证一下,计算完HashCode,还是不是偏向锁了

我们再次修改JOLExample3.java,具体代码如下:

 

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample3 {
static A a;

public static void main(String[] args) throws InterruptedException {
   //切记延迟一定要放在对象创建之前,不然是无效的,因为在你对象创建之前,偏向锁的延迟的时间
   //没有给你睡过去,这时候,对象已经创建了,对象头的信息已经生成了。
   //Thread.sleep(5000);
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   a.hashCode();

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

}

private static void sync() {
   synchronized (a) {
       out.println("lock ing");
       out.println(ClassLayout.parseInstance(a).toPrintable());
  }
}
}

同时关闭JVM中偏向锁的延迟,运行的结果如下:

在这里插入图片描述

我们可以发现:在before lock的时候是可偏向的状态,lock ing的时候变成了轻量锁,after lock 的时候变成了无锁,所以我们得出对象计算了HashCode,就不是偏向锁了。

看完了偏向锁的对象头,我们再来看看轻量锁的对象头,轻量级锁尝试在应用层面解决线程同步问题,而不触发操作系统的互斥操作,轻量级锁减少多线程进入互斥的几率,不能代替互斥。

创建JOLExample4.java,代码如下:

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample4 {
   static A a;
public static void main(String[] args) {
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
}

private static void sync() {
   synchronized (a) {
       out.println("lock ing");
       out.println(ClassLayout.parseInstance(a).toPrintable());
  }
}

}

运行结果如下:

在这里插入图片描述

可以得出:before lock 的时候是 00000001 无锁的状态,lock ing 的时候是 01010000 轻量锁的状态,after lock 的时候是 00000001 无锁的状态。

看完了轻量锁的对象头,我们再来看看重量锁的对象头,我们先创建一个JOLExample5.java具体代码如下:

 

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample5 {
   static A a;
public static void main(String[] args) throws InterruptedException {
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
   Thread t1 = new Thread(()->{
       synchronized (a) {
           try {
               Thread.sleep(5000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           out.println("t1 release");
      }
  });
   t1.start();
   
   Thread.sleep(1000);
   out.println("t1 lock ing");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
   sync();
   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   System.gc();
   out.println("after gc()");
   out.println(ClassLayout.parseInstance(a).toPrintable());
}

private static void sync() {
   synchronized (a) {
       out.println("main lock ing");
       out.println(ClassLayout.parseIstance(a).toPrintable());
  }
}}

运行结果如下:

在这里插入图片描述

在加锁之前(before lock)是 00000001 无锁,这时候t1来加锁,因为只有他一个线程所以轻量锁(t1 lock ing 00010000)由于t1在run方法中睡眠了5秒,这时候主线程也来尝试加锁,这个时候就是两个线程竞争了,所以是重量锁(main lock ing 00101010)

在这里插入图片描述

当结束的时候,还是重量锁(afteer lock 00101010),当执行一次gc操作过后发现变成了无锁但是年龄加了1(after gc() 00001001)

还有一点需要我们注意的就是:当调用wait方法会直接变成重量锁,我们来验证一下,创建JOLExample6.java,代码如下:

 

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample6 {
   static A a;
public static void main(String[] args) throws Exception {

   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   Thread t1 = new Thread(() -> {
       try {
           synchronized (a) {
               out.println("before wait");
               out.println(ClassLayout.parseInstance(a).toPrintable());
               a.wait();
               out.println("after wait");
               out.println(ClassLayout.parseInstance(a).toPrintable());
          }
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

  });
   t1.start();
   Thread.sleep(5000);
   synchronized (a) {
       a.notifyAll();
  }
}
}

运行结果如下:

在这里插入图片描述

可以看到在加锁之前是无锁的状态,执行wait方法之前是轻量锁,执行wait 方法之后,被唤醒的后是重量锁。

既然synchronized关键字有这三种锁,我们简单的比较它们之间的性能(粗略的比较下),书写以下的代码

public class A {
   int i;
   public synchronized void parse() {
       i++;
  }
}

//关闭偏向锁延迟‐XX:BiasedLockingStartupDelay=0
public class JOLExample7 {
   public static void main(String[] args) throws Exception {
       A a = new A();
       long start = System.currentTimeMillis();
       //调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
       //如果不出意外,结果灰常明显
       for (int i = 0; i < 1000000000L; i++) {
           a.parse();
      }
       long end = System.currentTimeMillis();
       System.out.println(String.format("%sms", end - start));
  }

}

 

先运行加上jvm参数关闭偏向锁延迟,就是偏向锁,然后运行的结果如下:

在这里插入图片描述

我们在开启偏向锁延迟就是轻量锁,然后运行结果如下:

在这里插入图片描述

最后我们在看重量锁,具体代码如下:

 

public class A {
    int i;
    public synchronized void parse() {
        JOLExample8.countDownLatch.countDown();
        i++;
    }
}



import java.util.concurrent.CountDownLatch;

public class JOLExample8 {
    static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
public static void main(String[] args) throws Exception {
    final A a = new A();

    long start = System.currentTimeMillis();

    //调用同步方法1000000000L 来计算1000000000L的++,对比各种锁的性能
    //如果不出意外,结果灰常明显
    for (int i = 0; i < 2; i++) {
        new Thread(() -> {
            while (countDownLatch.getCount() > 0) {
                a.parse();
            }
        }).start();
    }
    countDownLatch.await();
    long end = System.currentTimeMillis();
    System.out.println(String.format("%sms", end - start));
}
}

重量级锁的执行结果如下:

在这里插入图片描述

最后总结的结果如下:

偏向锁 轻量锁 重量锁
2355ms 23564ms 31227ms

偏向锁 轻量锁 重量锁 2355ms 23564ms 31227ms 最后我们再画个图总结下各种锁的对象头(只画出了最重要的部分,其他的省略)

在这里插入图片描述

 

不同情况下的锁升级策略

证明偏向锁

证明偏向锁 证明偏向锁之前,咱们按下图操作,给jvm添加查看全局配置的参数:

在这里插入图片描述

直接运行main方法,运行结果如下所示(由于篇幅问题,只截图了关键部分)

在这里插入图片描述

由图中的-XX:BiasedLockingStartupDelay=4000配置可知,jvm会在启动虚拟机之后的4s后才会开启偏向锁功能。知道这个概念后,咱们再来科普下什么是偏向锁。 所谓偏向锁:即当一把锁处于可偏向状态时,当有线程持有这把锁后,这把锁将偏向于这个线程。这里提到了可偏向状态,何为可偏向状态呢?可偏向状态是指在jvm开启可偏向功能后,new出来的一个对象它都是可偏向状态,即它的标识位为101,但是没有具体的偏向某一个线程。 证明可偏向状态和偏向锁: 添加如下代码并执行:

public class Valid {
public static void main(String[] args) throws InterruptedException {
    // 这里要注意, 一定要在创建对象之前睡眠,若我们先创建对象,可以想一想会发生什么情况!
    // 那肯定是不会启动偏向锁的功能呀,我们都知道加锁其实是给对象加了个标识
    // 如果我们在偏向锁功能未开启之前创建了对象,很抱歉,
    // jvm没有那么智能,后面不会去把这个对象改成可偏向状态(是偏向锁,但是没有偏向具体
    // 的线程)
    Thread.sleep(4100);

	System.out.println(ByteOrder.nativeOrder().toString());
    User user = new User();
    System.out.println("before lock");
    System.out.println(ClassLayout.parseInstance(user).toPrintable());

    synchronized (user) {
        System.out.println("lock ing");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }

    System.out.println("after lock");
    System.out.println(ClassLayout.parseInstance(user).toPrintable());
}
}

查看运行结果

在这里插入图片描述

证明一个对象调用了hashcode方法后无法再被标识为偏向锁,而是升级成轻量锁

编写如下代码(相对于上述代码,仅在加锁前调用了对象的hashcode方法):

public class Valid {

    public static void main(String[] args) throws InterruptedException {
        // 这里要注意, 一定要在创建对象之前睡眠,若我们先创建对象,可以想一想会发生什么情况!
        // 那肯定是不会启动偏向锁的功能呀,我们都知道加锁其实是给对象加了个标识
        // 如果我们在偏向锁功能未开启之前创建了对象,很抱歉,
        // jvm没有那么智能,后面不会去把这个对象改成可偏向状态(是偏向锁,但是没有偏向具体
        // 的线程)
        Thread.sleep(4100);

        System.out.println(ByteOrder.nativeOrder().toString());
        User user = new User();
        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());

        System.out.println(user.hashCode());

        synchronized (user) {
            System.out.println("lock ing");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }

        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

证明轻量锁

  • 这里说下轻量锁的概念:若线程是交替执行的,即上一个线程执行完释放锁后下一个线程再获取锁。若在jvm未开启偏向锁的过程中,对对象进行加锁时,对象直接是轻量锁。

public class Valid {

    public static void main(String[] args) throws InterruptedException {
        System.out.println(ByteOrder.nativeOrder().toString());

        User user = new User();
        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());

        synchronized (user) {
            System.out.println("lock ing");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }

        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

证明偏向锁膨胀为轻量锁

public class Valid {

    public static void main(String[] args) throws InterruptedException {
        // 开启偏向锁功能
        Thread.sleep(4100);
        System.out.println(ByteOrder.nativeOrder().toString());

        User user = new User();
        System.out.println("before lock" + ClassLayout.parseInstance(user).toPrintable());

        synchronized (user) {
            System.out.println("lock ing" + ClassLayout.parseInstance(user).toPrintable());
        }

        System.out.println("after lock" + ClassLayout.parseInstance(user).toPrintable());

        // 开启线程来获取锁
        Thread t1 = new Thread(() -> {
            synchronized (user) {
                System.out.println("other t1 thread get lock" + ClassLayout.parseInstance(user).toPrintable());
            }
        }, "t1");
        t1.start();
        // 等待t1执行完后再打印一次锁信息
        t1.join();

        System.out.println("after t1 thread release lock" + ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

证明重量锁

  • 重量锁概念:多个线程存在激烈的竞争时,锁会膨胀成重量锁,且不可逆!

  • 典型案例:生产者消费者模型:

public class ValidSynchronized {

    static Object lock = new Object();

    static volatile LinkedList<String> queue = new LinkedList<>();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());

        Consumer consumer = new Consumer();
        Producer producer = new Producer();

        consumer.start();
        producer.start();

        Thread.sleep(500);
        consumer.interrupt();
        producer.interrupt();

        // 睡眠3s ==> 目的是为了让锁自己释放,防止在释放过程中打印锁的状态出现重量锁的情况
        Thread.sleep(3000);
        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }
}

class Producer extends Thread {

    @Override
    public void run() {
        while (!isInterrupted()) {
            synchronized (ValidSynchronized.lock) {
                System.out.println("lock ing");
                System.out.println(ClassLayout.parseInstance(ValidSynchronized.lock).toPrintable());
                String message = UUID.randomUUID().toString();
                System.out.println("生产者生产消息:" + message);
                ValidSynchronized.queue.offer(message);
                try {
                    // 生产者自己wait,目的是释放锁
                    ValidSynchronized.lock.notify();
                    ValidSynchronized.lock.wait();
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    this.interrupt();
                }
            }
        }
    }
}

class Consumer extends Thread {

    @Override
    public void run() {
        while (!isInterrupted()) {
            synchronized (ValidSynchronized.lock) {
                if (ValidSynchronized.queue.size() == 0) {
                    try {
                        ValidSynchronized.lock.wait();
                        ValidSynchronized.lock.notify();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                String message = ValidSynchronized.queue.pollLast();
                System.out.println("消费者消费消息:" + message);

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    this.interrupt();
                }
            }
        }
    }
}

在这里插入图片描述

证明调用wait方法后,锁会升级为重量锁

public class ValidWait {

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(4100);

        final User user = new User();

        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());

        Thread t1 = new Thread(() -> {
            synchronized (user) {
                System.out.println("lock ing");
                System.out.println("before wait");
                System.out.println(ClassLayout.parseInstance(user).toPrintable());

                try {
                    user.wait();
                    System.out.println("after wait");
                    System.out.println(ClassLayout.parseInstance(user).toPrintable());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "t1");
        t1.start();

        // 主线程睡眠3s后,唤醒t1线程
        Thread.sleep(3000);
        System.out.println("主线程查看锁,变成了重量锁");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

总结

偏向锁和hashcode是互斥的,只能存在一个。 jvm默认对偏向锁功能是延迟加载的,大概时间为4s钟,可以添加JVM参数: -XX:BiasedLockingStartupDelay=0来设置延迟时间为0。偏向锁的延迟加载关闭后,基本上所有的锁都会为可偏向状态,即mark word为101,但是它还没有具体偏向的线程信息 偏向锁退出同步块后依然也是偏向锁 重量级锁之所以重量就是因为状态不停的切换,最终映射到代码层面就是不停的调用操作系统函数(最终会调用到jvm的mutex类) 调用锁对象的wait方法时,当前锁对象会立马升级为重量级锁 偏向锁只要被其他线程拿到了,此时偏向锁会膨胀。膨胀为轻量锁

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM