Java synchronized关键字的底层实现以及锁升级优化的原理一万字

Posted 刘Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java synchronized关键字的底层实现以及锁升级优化的原理一万字相关的知识,希望对你有一定的参考价值。

介绍了synchronized关键字实现锁的底层原理以及JDK对于synchronized做出的锁升级优化!

文章目录

1 syncronized基础知识

synchronized 块是Java 提供的一种原子性内置锁,Java中的每个对象都可以把它当作一个同步锁来使用,这些Java内置的使用者看不到的锁被称为内部锁,也叫作监视器锁。

线程的执行代码在进入synchronized 代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait 系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后, 其他线程必须等待该线程释放锁后才能获取该锁。

另外,由于Java 中的线程是与操作系统的原生线程一一对应的,所以当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作,而synchronized 的使用就会导致上下文切换。

1.1 Synchronized锁的特性

  1. 原子性:确保线程互斥的访问同步代码,如同单线程环境,自然具有原子性。
  2. 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
  3. 有序性:synchronized内的代码和外部的代码禁止排序,至于内部的代码,似乎不会禁止排序,但是由于是单线程的,根据Java代码单线程的有序性定义“不管怎么重排序,单线程的执行结果都不能改变”,所以没有任何影响!

1.2 synchronized锁表现形式

synchronized锁,具有三种表现形式:

  1. 对于普通同步方法,锁是当前实例对象this
  2. 对于静态同步方法,锁是当前类的Class的对象
  3. 对于同步方法块 为Syncronized括号的配置对象。

1.3 Mark Word

synchronized作为锁是和Java是对象头中的Mark Word密不可分的。Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。关于Java对象的构成,可以看这篇文章:Java中的对象内存布局、压缩指针、对象大小计算以及对象访问定位的详解

在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。32位JVM在不同状态下mark word的组成如下表:

状态Mark Word(32 bit)
无锁hash:25age:4biased_lock:1lock:2
偏向锁thread Id:23epoch:2age:4biased_lock:1lock:2
轻量级锁Lock record address:30(指向栈中锁记录的指针)lock:2
重量级锁Monitor address:30 (指向监视器对象/monitor的指针)lock:2
GC标记30,空,只在Mark Sweep GC中用到,其他时刻无效lock:2

在64位虚拟机下,Mark Word是64bit大小的,不同状态下mark word的组成如下表:

状态Mark Word(64 bit)
无锁unused:25hash:31unused:1age:4biased_lock:1lock:2
偏向锁thread Id:54epoch:2unused:1age:4biased_lock:1lock:2
轻量级锁Lock record address:62(指向获得锁的线程的栈中锁记录Lock record的指针)lock:2
重量级锁Monitor address:62 (指向锁对象关联的monitor对象的指针)lock:2
GC标记62,空,只在Mark Sweep GC中用到,其他时刻无效lock:2

下面来解释各种存放的数据的含义:
unused:就是表示没有使用的区域,在64位虚拟机的对象头中会出现。

hash:对象的hashcode,如果对象没有重写hashcode()方法,那么通过System.identityHashCode()方法获取。采用延迟加载技术,不会主动计算,但是一旦生成了hashcode,JVM会将其记录在markword中;

age:4位的Java对象GC年龄。对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15。最大值也是15。

biased_lock:1位的偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

lock:2位的锁状态标记位,其中无锁和偏向锁的锁标志位都是01,只是在前面的1 bit的biased_lock区分了这是无锁状态还是偏向锁状态。

lock和biased_lock共同表示对象处于什么状态

biased_lock 偏向锁标识位lock 锁标识位状态
001未锁定
101偏向锁
00轻量级锁
10重量级锁
11GC标记

thread Id:持有偏向锁的线程ID。

epoch:的偏向锁的时间戳

Lock record address:轻量级锁状态下,指向获得锁的线程的栈中锁记录Lock record的指针。

Monitor address:重量级锁状态下,指向锁对象关联的monitor对象的指针。

从上面的内容可以看出来,“锁”这个东西,可能是个锁记录+对象头里的引用指针(判断线程是否拥有锁时将线程的锁记录地址和对象头里的指针地址比较),也可能是对象头里的线程ID(判断线程是否拥有锁时将线程的ID和对象头里存储的线程ID比较),也可能是对象头里的Monitor指针和Monitor对象(重量级锁)。

JDK1.6及之后synchronized锁可以分为偏向锁、轻量级锁、重量级锁,并且前两种锁和Monitor是没关系的,这当然得益于JDK1.6对synchronized的优化,因此,人们常说的synchronized底层是使用Monitor来实现的这句话是不准确的。

1.4 Monitor

什么是Monitor?

  1. Monitor是一种用来实现同步的工具,又称为对象监视器/管程。
  2. 与每个java对象相关联,即每个java对象都有一个Monitor与之对应
  3. Monitor是实现Sychronized(内置锁)的基础,是shcronized作为重量级锁使用的对象(这源于JDK1.6的优化)。

在重量级锁阶段,线程在获取锁的时候,实际上就是获得一个锁对象关联的监视器对象 (Monitor)的所有权,Monitor可以认为是一个同步对象,所有的Java对象是天生携带一个Monitor,因此任何对象都可以作为锁。多个线程访问同步代码块时,相当于去争抢对象监视器对象、修改对象中的锁标识。更多Monitor详解在重量级锁那部分。

2 synchronized块的底层原理

案例:

public class SyncBlock 
    static int i;

    public static void main(String[] args) 
        synchronized (SyncBlock.class) 
            i++;
        
    


使用javap -v反编译后得到字节码如下:

如果没有synchronized块,而是普通块:

public class NoSyncBlock 
    static int i;

    public static void main(String[] args) 
        
            i++;
        
    

那么就不会产生monitor相关字节码:

从上面的字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。

当执行monitorenter指令时,当前线程将试图获取锁对象对应的Monitor的持有权,当锁对象的 Monitor的计数器为0,那线程可以成功取得 Monitor,并将计数器值设置为 1,取锁成功。

如果当前线程已经拥有锁对象的 Monitor的持有权,那它可以重入这个 Monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。

倘若其他线程已经拥有Monitor的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将尝试释放锁并设置Monitor的计数器值减一直到为0 ,其他线程将有机会持有 Monitor。

值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器表,这个异常处理器表声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。因此monitorenter 和 monitorexit指令并不是一对一的,可能是一对多的。

当然在JDK1.6及之后的JDK版本中,对上面这些步骤进行了优化升级,得到了更好的性能,上面的步骤只有重量级锁时才会使用到。

3 synchronized方法的底层原理

public class SyncMethod 
    static int i;
    public static void main(String[] args) 
    synchronized void sync() 
        i++;
    


方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。

当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。下面我们看看字节码层面如何实现:

从字节码中可以看出,synchronized修饰的方法并没有monitorenter指令和monitorexit指令,取得代之的确是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

在JDK1.6之前的同步块和同步方法两个同步方式实际都是通过获取Monitor和释放Monitor来实现同步的,但在JDK1.6时,规则改变了,下面来看看JDK1.6的锁升级!

在JDK1.6之前的同步块和同步方法两个同步方式实际都是通过获取Monitor和释放Monitor来实现同步的,其实wait、notiy和notifyAll等方法也是依赖于Monitor对象的内部方法来完成的,这也就是为什么需要在同步方法或者同步代码块中调用的原因(需要先获取对象的锁,才能执行),否则会抛出java.lang.IllegalMonitorStateException的异常。

在JDK1.6之前,synchronized属于重量级锁,效率低下,因为Monitor是依赖于底层的操作系统的互斥原语mutex来实现,JDK1.6之前实际上加锁时会调用Monitor的enter方法,解锁时会调用Monitor的exit方法,由于java的线程是映射到操作系统的原生线程之上的,如果要操作Monitor对象,都需要操作系统来帮忙完成,这会导致线程在“用户态和内核态”两个态之间来回切换,这个状态之间的转换需要相对比较长的时间,对性能有较大影响。

庆幸的是在JDK1.6之后Java官方对从JVM层面对synchronized较大优化,所以现在的synchronized锁效率也优化得很不错了,JDK1.6之后,为了减少获得锁和释放锁所带来的性能消耗,为了减少这种重量级锁的使用,引入了轻量级锁和偏向锁,这两个锁可以不依赖Monitor的操作。

4 synchronized的优化(升级)

JDK1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在JDK 1.6中,锁对象一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。

锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率,同时降低了实现复杂度。

4.1 偏向锁

HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下(即线程A尚未执行完同步代码块,线程B发起了申请锁的申请),则一定会转化为轻量级锁或者重量级锁。

引入偏向锁主要目的是:为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。因为轻量级锁、重量级锁的加锁、解锁操作是需要依赖多次CAS原子指令的,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗也必须小于节省下来的CAS原子指令的性能消耗)。

4.1.1 偏向锁的获取

  1. 检测锁对象的MarkWord是否为可偏向状态,即是否为偏向锁标识1,锁标识位为01,如果是,则执行步骤(2);否则,直接表示锁对象的Mark Word为不可偏向状态(关闭了偏向锁),则直接构造轻量级锁,然后往下执行同步代码;
  2. 若为可偏向状态,则判断Mark Word中的线程ID是否为当前线程ID,如果是,表示线程A已经获得了这个偏向锁,虚拟机不再进行任何同步操作,不需要进行CAS操作来加锁和解锁,提高性能,相当于获取到了锁,然后往下执行同步代码;否则执行步骤(3);
  3. 如果Mark Word的线程ID不为当前线程ID,则通过CAS操作竞争:CAS(0,current_tid),这里的CAS默认预期线程ID为0,即还没有任何线程获得过锁。CAS成功之后,则将Mark Word的线程ID修改为当前线程ID,表示获取到了锁,然后往下执行同步代码;否则执行步骤(4);
  4. 如果CAS失败,证明当前锁对象的偏向锁已被其他线程获取了,存在多线程竞争情况,此时可能出现重偏向或者锁升级的步骤。

4.1.2 偏向锁的撤销(升级)

获取偏向锁的线程线程是不会主动去释放偏向锁,也就是即使最开始的线程执行完毕了同步的内容,其保存在Mark Word中的线程ID也不会替换回来,需要等待其他线程来竞争,才有可能被被动的替换。

如果有第二个线程来获取偏向锁,但是CAS失败了,此时就会执行锁撤销的操作,锁撤销操作也就是锁升级操作,对偏向锁进行撤销时,需要等待持有锁的线程到达全局安全点。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,然后有一下两种情况:

  1. 如果原获得偏向锁的线程如果已经退出了临界区,也就是同步代码块执行完了,不存在锁竞争,那么这个时候争抢锁的线程可以直接进行锁升级。锁标志位改为00,将指向当前线程的锁记录地址的指针放入对象头Mark Word。当前线程获得轻量级锁。
  2. 如果原获得偏向锁的线程的同步代码块还没执行完,处于临界区之内,这个时候会把锁标志位改为00,同时原获得偏向锁的线程的锁记录地址的指针放入对象头Mark Word,后唤醒持有锁的线程,进入轻量级锁的竞争模式,此处将当前线程挂起再恢复原持有锁线程的过程中并没有发生锁的转移,仍然在之前的线程手中,只是穿插了个“将对象头中的线程ID变更为指向锁记录地址的指针” 这么个事。

除了有第二个线程尝试获取锁时会导致偏向锁撤销(升级)之外,下列情况也会导致偏向锁撤销:

  1. 当调用锁对象的Object#hashSystem.identityHashCode()方法时,会导致该对象的偏向锁直接升级为重量级锁。这是因为在Java中一个对象的hashcode是在调用这两个方法时才生成的,如果是无锁状态则存放在mark word中,如果是轻量级锁则存放在对应的线程的Lock Record栈帧中,如果是重量级锁则存放在对应的monitor中,而偏向锁是没有地方能存放该信息的,所以必须升级。
  2. 另外,执行锁对象的wait/notify/notifyall方法,会将其恢复成无锁状态,直接膨胀成重量级锁。执行Unsafe类的monitorenter/trymonitorenter/monitorexit方法,会将其恢复成无锁状态,直接膨胀成重量级锁。

4.1.3 偏向锁的关闭

偏向锁在JDK1.6之后是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

4.1.4 批量重偏向和批量撤销

4.1.4.1 批量重偏向

批量重偏向:当创建了大量同类型对象并且一个线程对它们执行了初始的同步操作,而后来其他的线程也来将这些对象作为锁对象进行同步操作,可能会导偏向锁重偏向。

通过BiasedLockingBulkRebiasThreshold参数可以设置批量重偏向的阈值,默认为20,当达到第19次撤销偏向的时候,后续操作将会执行重偏向操作,即此时锁不会升级为轻量级锁,它还是偏向锁,只是偏向线程id更新成了当前线程的id

如下案例,假设有一个锁对象类:

public class LockA 

如下案例,我们为LockA创建30个锁实例,并且使用线程t1依次进行锁定,随后使用线程t2再依次进行锁定,我们借助jol打印此时锁对象的对象头信息,加入启动参数-XX:BiasedLockingStartupDelay=0来关闭偏向锁延迟:

public class BiasedLock 

    static Thread t1;
    static Thread t2;

    public static void main(String[] args) throws Exception 
        List<LockA> list = new ArrayList<>();
        t1 = new Thread(() -> 
            for (int i = 0; i < 30; i++) 
                LockA d = new LockA();
                list.add(d);
                synchronized (d) 
                    System.out.println((i + "\\t" + JolUtils.toPrintableSimple(d)));
                
            
            LockSupport.unpark(t2);
        , "t1");
        t1.start();
        t2 = new Thread(() -> 
            LockSupport.park();
            System.out.println(("===============> "));

            for (int i = 0; i < 30; i++) 
                LockA d = list.get(i);
                System.out.println((i + "\\tbefore" + "\\t" + JolUtils.toPrintableSimple(d)));
                synchronized (d) 
                    System.out.println((i + "\\tcenter" + "\\t" + JolUtils.toPrintableSimple(d)));
                
                System.out.println((i + "\\tafter" + "\\t" + JolUtils.toPrintableSimple(d)));
            
        , "t2");
        t2.start();
    

结果如下:

0	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
1	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
2	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
3	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
4	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
5	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
6	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
7	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
8	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
9	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
10	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
11	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
12	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
13	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
14	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
15	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
16	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
17	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
18	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
19	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
20	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
21	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
22	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
23	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
24	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
25	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
26	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
27	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
28	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
29	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
===============> 
0	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
0	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
0	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
1	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
1	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
1	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
2	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
2	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
2	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
3	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
3	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
3	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
4	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
4	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
4	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
5	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
5	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
5	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
6	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
6	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
6	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
7	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
7	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
7	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
8	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
8	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
8	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
9	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
9	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
9	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
10	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
10	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
10	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
11	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
11	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
11	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
12	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
12	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
12	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
13	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
13	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
13	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
14	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
14	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
14	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
15	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
15	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
15	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
16	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
16	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
16	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
17	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
17	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
17	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
18	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
18	center	00000000 00000000 00000000 00000000 00100000 11001111 11110010 00000000
18	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
19	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
19	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
19	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
20	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
20	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
20	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
21	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
21	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
21	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
22	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
22	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
22	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
23	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
23	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
23	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
24	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
24	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
24	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
25	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
25	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
25	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
26	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
26	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
26	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
27	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
27	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
27	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
28	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
28	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
28	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
29	before	00000000 00000000 00000000 00000000 00100000 01001000 10001000 00000101
29	center	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101
29	after	00000000 00000000 00000000 00000000 00100000 01001000 10101001 00000101

可以看到,在线程t1加锁之后,锁对象的Mark Word前面变成了当前线程的ID,此时是偏向锁,而在t2加锁之后,在第0-18个对象,即前19个对象之中,锁升级为了轻量级锁,锁标志位000,轻量级锁解锁之后变成了001,即不可偏向。

而从第20个对象开始,锁并没有升级,他还是偏向锁,只不过此时偏向线程id变成了线程t2,这就是批量重偏向。

注意:偏向锁重偏向一次之后不可再次重偏向,只能撤销升级为轻量级锁。

批量重偏向是针对一个类的对象,计数方式也是以类为单位的,假设有另外一个锁对象LockB:

public class LockB  

我们将刚才的程序改为分别用15个LockA和LockB对象进行同步,加入启动参数-XX:BiasedLockingStartupDelay=0来关闭偏向锁延迟:

public class BiasedLock2 

    static Thread t1;
    static Thread t2;

    public static void main(String[] args) throws Exception 
        List<Object> list = new ArrayList<>();
        t1 = new Thread(() -> 
            for (int i = 0; i < 30; i++) 
                if (i % 2 == 0) 
                    LockA d = new LockA();
                    list.add(d);
                    synchronized (d) 
                        System.out.println((i + "\\t" + JolUtils.toPrintableSimple(d)));
                    
                 else 
                    LockB d = new LockB();
                    list.add(d);
                    synchronized (d) 
                        System.out.println((i + "\\t" + JolUtils.toPrintableSimple(d)));
                    
                

            
            LockSupport.unpark(t2);
        , "t1");
        t1.start();
        t2 = new Thread(() -> 
            LockSupport.park();
            System.out.println(("===============> "));

            for (int i = 0; i < 30; i++) 
                Object d = list.get(i);
                System.out.println((i + "\\tbefore" + "\\t" + JolUtils.toPrintableSimple(d)));
                synchronized (d) 
                    System.out.println((i + "\\tcenter" + "\\t" + JolUtils.toPrintableSimple(d)));
                
                System.out.println((i + "\\tafter" + "\\t" + JolUtils.toPrintableSimple(d)));
            
        , "t2");
        t2.start();
    

测试结果如下:

0	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
1	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
2	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
3	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
4	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
5	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
6	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
7	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
8	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
9	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
10	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
11	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
12	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
13	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
14	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
15	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
16	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
17	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
18	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
19	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
20	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
21	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
22	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
23	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
24	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
25	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
26	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
27	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
28	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
29	00000000 00000000 00000000 00000000 00100000 01101111 11101111 11011000
===============> 
0	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
0	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
0	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
1	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
1	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
1	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
2	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
2	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
2	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
3	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
3	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
3	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
4	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
4	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
4	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
5	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
5	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
5	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
6	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
6	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
6	after	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
7	before	00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
7	center	00000000 00000000 00000000 00000000 00100000 01111111 11110100 11000000
7	after	00000000 00000000 00000000 0000000

以上是关于Java synchronized关键字的底层实现以及锁升级优化的原理一万字的主要内容,如果未能解决你的问题,请参考以下文章

synchronize底层实现原理

Java同步关键字synchronize底层实现原理

Java锁synchronized关键字学习系列之重量级锁

java多线程synchronized底层实现

synchronized原理是啥?

Java并发编程专题系列之深入分析synchronized(基础篇)