synchronized 关键字解析


在jdk1.6之前,synchronized是重量级锁,在jdk1.6级以后jvm对锁的实现进行了优化,如使用偏向锁、轻量级锁等技术来减少锁操作的开销。
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。

1、Syncronized应用

在java代码中synchronized关键字可以使用在代码块和方法中,根据Synchronized用的位置可以有以下使用场景:

分类 具体分类被锁对象 伪代码
方法静态方法类对象//静态方法,锁住的是类对象
public synchronized static void method(){}
实例方法类的实例对象 //实例方法,锁住的是该类的实例对象
public synchronized void method(){
}
代码块实例对象类的实例对象//同步代码块,锁住的是该类的实例对象
synchronized (this){ }
class对象类对象 //同步代码块,锁住的是该类的类对象
synchronized(Test.class){}
任意实例对象Object实例对象Object Client client=new Client();
//同步代码块,锁住的是配置的实例对象,锁对象为client
synchronized(client){}

由表可知,synchronized既可以修饰方法也可以修饰代码块,具体详见上表。这里的需要注意的是:如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系

2、JAVA对象头

说到锁,不得不说到java的对象头,对象头中包含了一些关于锁的字段。了解对象头对于学习synchronized锁有极大的帮助。
在这里插入图片描述
由图可以知道,在64位的hotspot虚拟机下,java的对象头包括两部分信息,第一部分是Mark Word,主要存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,偏向线程ID等内容。对象头的另外一部分就是类型指针(klass Word),虚拟机通过这个指针确定该对象是哪个类的实例。

在这里我们主要研究Mark Word,由图可以知道,给对象上锁,其实就是改变对象头的状态,各个锁的状态如图所示。
在这里插入图片描述

3、Synchronized的原理

3.1、什么是monitor

我们可以把它理解为一个同步工具,也可以描述为一种同步机制。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下:

ObjectMonitor() {
   _header       = NULL;
   _count        = 0; //记录个数
   _waiters      = 0,
   _recursions   = 0;
   _object       = NULL;
   _owner        = NULL;
   _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
   _WaitSetLock  = 0 ;
   _Responsible  = NULL ;
   _succ         = NULL ;
   _cxq          = NULL ;
   FreeNext      = NULL ;
   _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
   _SpinFreq     = 0 ;
   _SpinClock    = 0 ;
   OwnerIsThread = 0 ;
 }

_owner:指向持有ObjectMonitor对象的线程
_WaitSet:存放处于wait状态的线程队列
_EntryList:存放处于等待锁block状态的线程队列
_count:用来记录该线程获取锁的次数

也就是说ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当有多个线程访问同一块同步代码块的时候,线程会线程会进入_EntryList,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor。

3.2、Synchronized修饰代码块

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

编译上述代码并使用javap反编译后得到字节码如下:
在这里插入图片描述
monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;

monitorexit:执行monitorexit的线程必须是monitor所对应持有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

需要说明的是:monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁。

通过上面的描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因

3.3、Synchronized修饰实例方法

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}

查看反编译后结果:
在这里插入图片描述
从编译的结果来看,方法的同步并没有通过指令 monitorenter 和 monitorexit 来完成,是隐式同步,JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先持有monitor,然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。

4、线程中的start方法时如何调用run方法的

我们知道,synchronized同步锁,是离不开线程的,那么线程时如何执行的呢?
首先我们启动一个线程需要调用start方法,进入start方法,可以知道start方法会调用本地方法start0,编译openJDK源码可以知道,这个start0会执行Thread.c文件请求hotspot创建javaThread方法,最终调用操作系统函数pthread_create方法,pthread_create方法需要传入四个参数,第三个参数就是线程的主体方法(参数为java_start),在这个主体函数里面 通过某种方式调用java的run方法。

也就是说Java执行线程,最终调用操作系统函数。Java创建一个线程等于操作系统创建一个线程。

5、notify和notifyAll的区别

当一个线程进入wait之后,就必须等待其他线程notify/notifyAll,使用notifyAll可以唤醒所有处于wait状态的线程,使其重新进入锁的竞争队列中,使用notify只能唤醒一个。如果没把握,建议 notifyAll,防止 notify因为信号丢失而造成程序异常。

6、偏向锁

在学习偏向锁之前,我们先了解一下一些关于偏向锁的JVM参数设置信息。通过-XX:+PrintFlagsFinal 查看jvm默认参数,其中有这么一段:
在这里插入图片描述
BiasedLockingStartupDelay=4000:这是偏向锁启动延迟的参数,默认的情况下为延迟4s。

BiasedLockingBulkRdbiasThreshold=20: 这是偏向锁批量重偏向的阈值,在后面的章节中会进行说明。

BiasedLockingBulkRevokeThreshold=40:这是偏向锁批量撤销的阈值,在后面的章节中会进行说明。

我们可以通过 -XX:BiasedLockingStartupDelay=0 来关闭偏向锁延迟。通过 -XX:-UseBiasedLocking=false 关闭偏向锁,程序默认会进入轻量级锁状态。

那么为什么JVM虚拟机在启动的时候会会将偏向锁默认延迟4s呢?
因为程序执行的时候会先启动jvm、gc等线程,多个线程抢占资源,所以存在资源竞争,所以如果不取消偏向锁,就会不断的进行锁升级,消除偏向锁,这些过程会浪费资源,所以直接取消偏向锁,4s以后开启偏向锁。

偏向锁的引入

HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

通过JOL获取偏向锁对象头信息,添加 -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 来禁用偏向锁延迟:

public class A {
    boolean flag=false;
}
public class JOLExample2 {
   static A a;
    public static void main(String[] args) {
       a=new A();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

运行结果为:
在这里插入图片描述
我们的计算机中存储数据是以小端的方式,所谓小端模式(Little-endian), 是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内在的低地址中,这种存储模式将地址的高低和数据位 权有效结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致;

因此上图中画红色框的地方就是Mark Word中后8位,这8位的后三位便是偏向锁标识(1bit)和锁的标志位(2bit)

before lock 、lock ing 和after lock的结果都为00000101,但是before lock 下没有线程持有锁,只有一个偏向状态。后面两个都有线程持有锁(都有线程id,并且相同)。也就是说在偏向锁释放锁之后,在没有其他线程获取锁的情况下,依然持有这个偏向锁。

偏向锁的获取

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

偏向锁的撤销

加锁和对象的hashCode的关系

我们知道,在有锁状态下,Mark Word的前56bit位置被锁指针或者线程id占用,对象的hashCode存到哪里呢,还是不存在hashCode了呢?
先看一段代码,在加锁之前计算对象hashCode值:

public class JOLExample8 {
   static A a;
    public static void main(String[] args) throws Exception {
        //保证偏向锁已经被开启
        Thread.sleep(5000);
        a= new A();
        a.hashCode();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

运行结果:

befor lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 9d e1 40 (00000001 10011101 11100001 01000000) (1088527617)
      4     4        (object header)                           15 00 00 00 (00010101 00000000 00000000 00000000) (21)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

lock ing
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           40 f2 89 02 (01000000 11110010 10001001 00000010) (42594880)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 9d e1 40 (00000001 10011101 11100001 01000000) (1088527617)
      4     4        (object header)                           15 00 00 00 (00010101 00000000 00000000 00000000) (21)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

before lock:无锁; lock ing:轻量锁 after lock:无锁。说明如果对象已经计算了hashcode就不能偏向了。

下面再看一段代码,当线程处于偏向锁状态,需要计算HashCode的情况:

public class JOLExample8 {
   static A a;
    public static void main(String[] args) throws Exception {
        //保证偏向锁已经被开启
        Thread.sleep(5000);
        a= new A();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("lock ing");
            a.hashCode();
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

运行结果:

befor lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

lock ing
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           2a 13 3a 26 (00101010 00010011 00111010 00100110) (641340202)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           2a 13 3a 26 (00101010 00010011 00111010 00100110) (641340202)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

由结果可知,lock ing的时候 锁状态为010 重量锁。因此当一个对象当前正处于偏向锁状态,并且需要计算其hash code的话,则它的偏向锁会被撤销,并且锁会膨胀为重量锁。

下面再看一段代码,当一个对象当前正处于偏向锁状态,调用wait方法的情况:

public class JOLExample9 {
    static A a;
    public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());

        Thread t1 = new Thread() {
            public void run() {
                synchronized (a) {
                    try {
                        synchronized (a) {
                            System.out.println("before wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                            a.wait();
                            System.out.println(" after wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(7000);
        synchronized (a) {
            a.notifyAll();
        }
    }
}

运行结果:

befre lock
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

before wait
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 10 5f 29 (00000101 00010000 01011111 00101001) (694095877)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 after wait
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           9a 33 7d 26 (10011010 00110011 01111101 00100110) (645739418)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

根据结果可知,调用wait方法后,会变成010重量锁。

总结:

  • 当一个对象已经计算过hash code,它就无法进入偏向锁状态;
  • 当一个对象当前正处于偏向锁状态,并且需要计算其 hash code的话,则它的偏向锁会被撤销,并且锁会膨胀为重量锁;
  • 当一个对象当前正处于偏向锁状态,调用wait方法则立刻变成重量锁。
  • 重量锁的实现中,ObjectMonitor类里有字段可以记录非加锁状态下的mark word,其中可以存储hash code的值。或者简单说就是重量锁可以存下hash code。可以通过这个链接查看ObjectMonitor对象源码:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/8c0fa90986a6/src/share/vm/runtime/objectMonitor.hpp

7、轻量锁

当多个线程竞争锁时,偏向锁就会撤销,偏向锁撤销之后会升级为轻量级锁。这时候的多线程竞争锁是指 多个线程交替执行,多个线程可能不是顺序执行,会自旋一个线程上下文的时间,再去获取锁,也会是轻量级锁。
多个线程交替执行有以下几种方式:
I、第一个线程结束,第二个线程执行 没有竞争——偏向锁膨胀为轻量级锁
II、第一个线程 同步结束,线程未结束,第二个线程执行 ——没有竞争——偏向锁膨胀为轻量级锁。

8、重量锁

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。

9、偏向锁、轻量级锁、重量级锁性能对比

我们测试一下调用同步方法10亿来计算10亿的i++,对比偏向锁和轻量级锁、重量级锁的性能。

public class A {
    public synchronized void parse(){
        JOLExample6.countDownLatch.countDown();
    }
}

首先设置关闭偏向锁延迟的情况下运行线面代码测试偏向锁性能。(添加 -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 来禁用偏向锁延迟)。再开启偏向锁延迟运行下面代码测试轻量锁性能。

public class JOLExample4 {
    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));
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }

测试重量级锁性能:

public class JOLExample6 {
    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(){
                @Override
                public void run() {
                    while (countDownLatch.getCount() > 0) {
                        a.parse();
                    }
                }
            }.start();
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
}}	

运行结果如下所示:

偏向锁轻量锁重量锁
3553ms29075ms61359ms

由表可知,在性能上面,偏向锁>轻量锁>重量锁。

10、锁的批量重偏向和批量撤销重偏向

前面提到了JVM关于锁的几个默认参数中有BiasedLockingBulkRdbiasThreshold=20: 偏向锁默认批量重偏向的阈值。以及
BiasedLockingBulkRevokeThreshold=40:偏向锁默认批量撤销的阈值。我们可以通过代码进行验证。

public class JOLExample12 {
    static List<A> list = new ArrayList<A>();
    static List<A> list2 = new ArrayList<A>();
    public static void main(String[] args) throws Exception {

        Thread t1 = new Thread() {
            public void run() {
                for (int i=0;i<100;i++){
                    A a = new A();
                    synchronized (a){
                        list.add(a);
                        list2.add(a);
                    }
                }

            }

        };
        t1.start();
        t1.join();
        out.println("befre t2");
        out.println("list.get(1) 是偏向锁:"+ClassLayout.parseInstance(list.get(1)).toPrintable());
        out.println("list.get(50) 是偏向锁:"+ClassLayout.parseInstance(list.get(50)).toPrintable());
        Thread t2 = new Thread() {
            public void run() {
                for (int i = 0; i < list.size(); i++){
                    A a = list.get(i);
                    synchronized (a){
                        //i=10 未达到批量重偏向的阈值20,还是轻量级锁
                       if (i==10){
                           out.println("轻量级锁 t2 ing  ------------"+i );
                           out.println(ClassLayout.parseInstance(a).toPrintable());

                       }
                        //i=19 达到批量重偏向的阈值20,撤销为偏向锁
                       if (i==19){
                           out.println("偏向锁 t2 ing  ------------"+i );
                           out.println(ClassLayout.parseInstance(a).toPrintable());
                           //到此为止,累计重偏向20次
                           System.out.println("到此为止,a对象累计重偏向20次");

                       }

                        //i=24 超过批量重偏向的阈值20,都为偏向锁
                        if (i==24){
                            out.println("偏向锁 t2 ing  ------------"+i );
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }
                   }
                }
            }
        };
        t2.start();
         t2.join();

        //添加多余线程,防止线程ID复用
        new Thread() {
            public void run() {
                System.out.println("tmp---------------------------------------------");
            }
        }.start();

        Thread t3 = new Thread() {
            public void run() {
                for (int i = 20; i < list.size(); i++) {
                    A a=list.get(i);

                    if (i==20){
                        out.println("t3 ing 未加锁状态下,预期是t2的偏向锁  ------------" +i);
                        out.println(ClassLayout.parseInstance(a).toPrintable());

                    }

                    synchronized (a){
                        //i=20 没有达到批量撤销偏向锁的阈值40 是轻量锁
                        if (i==20){
                            out.println("轻量级锁 t3 ing  ------------" +i);
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }
                        //i=40 加上前面的20次,累计达到批量撤销偏向锁的阈值40 撤销t2偏向锁 升级为轻量级锁
                        if (i==40){
                            out.println("撤销t2偏向锁 升级为轻量级锁 t3 ing  ------------" +i);
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }
                        //i=25 累计达到批量撤销偏向锁的阈值40 撤销t2偏向锁 升级为轻量级锁
                        if (i==50){
                            out.println("轻量级锁 t3 ing  ------------" +i);
                            out.println(ClassLayout.parseInstance(a).toPrintable());

                        }

                    }
                }

            }
        };
        t3.start();
    }
}

运行结果如下:

befre t2
list.get(1) 是偏向锁:com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 30 22 28 (00000101 00110000 00100010 00101000) (673329157)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

list.get(50) 是偏向锁:com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 30 22 28 (00000101 00110000 00100010 00101000) (673329157)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

轻量级锁 t2 ing  ------------10
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           60 f0 95 29 (01100000 11110000 10010101 00101001) (697692256)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

偏向锁 t2 ing  ------------19
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 81 10 29 (00000101 10000001 00010000 00101001) (688947461)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

到此为止,a对象累计重偏向20次
偏向锁 t2 ing  ------------24
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 81 10 29 (00000101 10000001 00010000 00101001) (688947461)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

tmp---------------------------------------------
t3 ing 未加锁状态下,预期是t2的偏向锁  ------------20
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 81 10 29 (00000101 10000001 00010000 00101001) (688947461)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

轻量级锁 t3 ing  ------------20
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           20 f2 b5 29 (00100000 11110010 10110101 00101001) (699789856)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

撤销t2偏向锁 升级为轻量级锁 t3 ing  ------------40
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           20 f2 b5 29 (00100000 11110010 10110101 00101001) (699789856)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

轻量级锁 t3 ing  ------------50
com.luban.layout.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           20 f2 b5 29 (00100000 11110010 10110101 00101001) (699789856)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           61 c2 00 f8 (01100001 11000010 00000000 11111000) (-134167967)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t1给对象a加锁,list中的a对象全部是t1持有的偏向锁100个。t2执行加锁的过程中拿到t1持有的偏向锁,会撤销t1偏向锁,升级为t2轻量级锁,t2经过20次锁撤销,将后面的80个t1持有的偏向锁全部转换为为t2持有的偏向锁(20前面的锁不会变,还是t2持有的轻量锁,20次以后 就只会进行if判断,到100次)。

这时候如果t3来加锁,假如从20开始,将t2持有的偏向锁进行撤销,升级为t3轻量锁,当达到40的阈值后(也就是类对的对象累计撤销40次),根据jvm的设置,不会将t3的轻量锁重偏向为偏向锁,而是将40以后的t2的偏向锁撤销,升级为t3持有的轻量锁。

总结:以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向锁撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就会认为该class 的偏向锁有问题,因此会进行批量重偏向。每个class 对象会有一个对应的epoch字段,每个处于偏向锁状态对象的Mark Word中也有该字段,其初始值设置为创建该对象时,class中的epoch值,每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程,找到该class所有正处于加锁状态的偏向锁,将其epoch值改为新值,下次获取锁时,当发现当前对象的epoch值和class的epoch值不相等,那就算当前已经偏向了其他线程,也不会进行撤销操作,而是直接通过CAS操作箱其Mark Word的Thread id改为当前线程id。

11、锁的膨胀

锁的膨胀过程就是 偏向锁 到轻量锁 到 重量锁的过程。下面盗用一张图来说明具体流程。

在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值