目录

偏向锁

用途

偏向状态


偏向延迟

加锁

锁撤销 

调用对象 hashcode

其他线程使用该对象锁

wait / notify 

批量重偏向

批量撤销 

锁粗化 && 锁消除


偏向锁

用途

public void method1(){ synchronized(object){ method2(); } } public void method2(){ synchronized(object){ method3(); } } public void method3(){ synchronized(object){ // 执行代码 } }

没有线程竞争时,持锁线程反复获取锁(锁重入);每次都会生成锁记录以及进行 CAS 操作

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java

Java6 引入偏向锁进行优化;第一次 CAS 操作,将线程 ID 设置到对象头 Mark Word 中;之后,查看线程 ID 为自己,则表示无竞争,不进行 CAS 操作;以后,只要没有竞争,锁归该线程所有

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_加锁_02

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_加锁_03

偏向状态

 一个对象创建时:

① 如果开启了偏向锁(默认开启),Mark Word 的值为 0x05 即二进制后三位 101;thread、epoch、age 均为 0

② 如果未开启,Mark Word 的值为 0x01 即二进制后三位 001;hashcode、age 均为 0

③ 偏向锁默认延迟开启,不会立即生效

BiasedLockingStartupDelay=4000 // 默认延迟 4s -XX:BiasedLockingStartupDelay=0 // 加 VM 参数来禁用延迟

偏向延迟

① 默认延迟 (001)正常无锁状态

@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }

 ② 睡 5s 后(101)变为偏向锁

@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); MyInteger myInteger = new MyInteger(0); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_加锁_04

加锁

① 直接创建然后加锁(00)轻量级锁

@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); synchronized (myInteger){ log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } } }

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java_05

101)变为偏向锁

@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); MyInteger myInteger = new MyInteger(0); synchronized (myInteger){ log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } } }

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_加锁_06

锁撤销 

调用对象 hashcode

 原因:偏向锁状态下,Mark Word 要用 54 位来存线程 ID,没有位置存 hashcode

无锁状态下:存于 Mark Word 中(初始为 0)

量级锁:hashcode 存于锁记录中

量级锁:hashcode 存于 monitor 中

@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); log.debug("--------------------------------调用 hashcode 前--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); myInteger.hashCode(); log.debug("--------------------------------调用 hashcode 后--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_加锁_07

其他线程使用该对象锁

原因:多个线程获取同一把锁,但是没有竞争,交错获取;初始为偏向锁,其他线程获取时,会将其升级为轻量级锁(一般情况)

@Slf4j(topic = "c.MarkWord") public class MarkWord { public static void main(String[] args) throws InterruptedException { MyInteger myInteger = new MyInteger(0); new Thread(() -> { synchronized (myInteger){ log.debug("--------------------------------线程 t 使用该对象锁--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } }, "t").start(); Thread.sleep(5000); log.debug("--------------------------------线程 t 使用结束--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); synchronized (myInteger){ log.debug("--------------------------------main 使用该对象锁--------------------------------"); log.debug(ClassLayout.parseInstance(myInteger).toPrintable()); } } }

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java_08

wait / notify 

原因:wait / notify 只有重量级锁才能使用;调用之后,升级为重量级锁

@Slf4j(topic = "c.MarkWord")
public class MarkWord {
    public static void main(String[] args) throws InterruptedException {
        MyInteger myInteger = new MyInteger(0);
        new Thread(() -> {
            synchronized (myInteger){
                log.debug("--------------------------------线程 t 使用该对象锁--------------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
                log.debug("--------------------------------线程 t 调用 wait--------------------------------");
                try {
                    myInteger.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("--------------------------------线程 t wait 结束--------------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
            }
        }, "t").start();

        Thread.sleep(5000);
        synchronized (myInteger){
            log.debug("--------------------------------main 使用该对象锁--------------------------------");
            log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
            log.debug("--------------------------------main 调用 notify--------------------------------");
            myInteger.notify();
        }
    }
}

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_并发编程_09

批量重偏向(难点)

BiasedLockingBulkRebiasThreshold=20 // 开启批量重偏向的阈值

① 同一个类,它对象的偏向锁被撤销达到 20 次,则会进行批量重偏向(从第二十次开始)

② 线程第一次遇到偏向锁都会先撤销,批量重偏向不算做锁撤销

③ 如一开始,偏向锁偏向 t1,它有机会偏向其他线程;重偏向之后,保存的线程 ID 会重置

@Slf4j(topic = "c.MarkWord")
public class MarkWord {
    public static void main(String[] args) throws InterruptedException {
        List<MyInteger> list = new ArrayList();

        new Thread(() -> {
            for(int i = 0; i < 30; i++){
                MyInteger myInteger = new MyInteger();
                list.add(myInteger);
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
                synchronized (myInteger){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
            }

            synchronized (MyInteger.class){
                MyInteger.class.notify();
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (MyInteger.class){
                try {
                    MyInteger.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            for(int i = 0; i < list.size(); i++){
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                synchronized (list.get(i)){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
            }
        }, "t2").start();
    }
}

1. 初始化,线程 t1 中给这 30 个对象加的都是偏向锁 

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java 偏向锁什么时候升级为轻量锁_10

2. 然后,线程 t2 获取这 30 把锁时,0 ~ 18 都升级为轻量级锁,19 ~ 29 都重新偏向 t2

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_synchronized_11

 

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java_12

 3. 这样操作,在 t3 里撤销第 20 次,则 19 ~ 29 都会重新偏向 t3

@Slf4j(topic = "c.MarkWord")
public class MarkWord {
    static Thread t1, t2, t3;

    public static void main(String[] args) throws InterruptedException {
        List<MyInteger> list = new ArrayList();

         t1 = new Thread(() -> {
            for(int i = 0; i < 30; i++){
                MyInteger myInteger = new MyInteger();
                list.add(myInteger);
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
                synchronized (myInteger){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
            }

            LockSupport.unpark(t2);
        }, "t1");
         t1.start();

        t2 = new Thread(() -> {
            LockSupport.park();

            for(int i = 0; i < 19; i++){
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                synchronized (list.get(i)){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
            }
            LockSupport.unpark(t3);
        }, "t2");
        t2.start();

        t3 = new Thread(() -> {
            LockSupport.park();

            for(int i = 19; i < list.size(); i++){
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                synchronized (list.get(i)){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
            }
        }, "t3");
        t3.start();
    }
}

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_并发编程_13

批量撤销(难点)

BiasedLockingBulkRevokeThreshold=40 // 开启批量撤销的阈值 BiasedLockingDecayTime=25000 // 达到阈值就马上进行批量撤销的时间范围

① 锁撤销次数达到 40 次并且在 25s 内,则开始批量撤销

② 批量撤销:整个类的对象都不可偏向,包括新建的对象

③ 时间 >= 25s 时,重置在 [20, 40) 内的次数,可起到再次批量重偏向的作用

1. 重偏向时,不算做撤销;如:即使到第 100 把锁,都还是重偏向

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_加锁_14

2.1. 创建 39 把锁(开始重偏向只撤销了 19 把锁,第 20 把还是偏向锁,还剩 20 把偏向锁); 

2.2. 由之前的案例可得:0 ~ 18 在 t2 中升级为轻量级锁,19 ~ 38 仍然为偏向锁; 

2.3. t3 中,0 ~ 18 不会影响撤销数(已经为轻量级锁),19 ~ 38 依次被撤销,撤销数达到 40 开始批量撤销;新对象初始态为无锁状态

@Slf4j(topic = "c.MarkWord")
public class MarkWord {
    static Thread t1, t2, t3;

    public static void main(String[] args) throws InterruptedException {
        List<MyInteger> list = new ArrayList();

         t1 = new Thread(() -> {
            for(int i = 0; i < 39; i++){
                MyInteger myInteger = new MyInteger();
                list.add(myInteger);
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
                synchronized (myInteger){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
            }

            LockSupport.unpark(t2);
        }, "t1");
         t1.start();

        t2 = new Thread(() -> {
            LockSupport.park();

            for(int i = 0; i < list.size(); i++){
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                synchronized (list.get(i)){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
            }
            LockSupport.unpark(t3);
        }, "t2");
        t2.start();

        t3 = new Thread(() -> {
            LockSupport.park();

            for(int i = 0; i < list.size(); i++){
                log.debug(i + "---------------------------加锁前---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                synchronized (list.get(i)){
                    log.debug(i + "---------------------------加锁中---------------------------");
                    log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
                }
                log.debug(i + "---------------------------加锁后---------------------------");
                log.debug(ClassLayout.parseInstance(list.get(i)).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(new MyInteger()).toPrintable()); // 新建对象,查看状态
        }, "t3");
        t3.start();
    }
}

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java_15

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java 偏向锁什么时候升级为轻量锁_16

3. 撤销数在 [20, 40) 内,超过 25s,撤销数已重置;次数不在该范围,不会重置

if(i == 38){ // 使其 25s 内完成不了 try { Thread.sleep(26000); } catch (InterruptedException e) { e.printStackTrace(); } }

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_Java_17

Java 偏向锁什么时候升级为轻量锁 synchronized偏向锁_synchronized_18

锁粗化 && 锁消除

锁粗化:加锁、解锁会耗损性能;对锁不要过度细化,有时需要将锁范围扩大

锁消除:指的是虚拟机既时编辑器在运行时候,对一些代码上要求同步,但是对被检测到不可能存在共享数据竞争的锁进行一个消除(依据逃逸分析)。