并发编程 synchronized (六) 偏向锁

本文深入探讨了Java中的偏向锁机制,包括其用途、偏向状态、延迟启动、加锁过程、锁撤销的原因及场景,如调用对象的hashCode和多线程使用同一对象锁。同时,详细阐述了批量重偏向和批量撤销的条件及影响,并通过实例展示了锁粗化和消除的概念,以提升并发性能。

目录

偏向锁

用途

偏向状态

偏向延迟

加锁

锁撤销 

调用对象 hashcode

其他线程使用该对象锁

wait / notify 

批量重偏向

批量撤销 

锁粗化 && 锁消除


偏向锁

用途

public void method1(){
    synchronized(object){
        method2();
    }
}

public void method2(){
    synchronized(object){
        method3();
    }
}

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

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

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

偏向状态

 一个对象创建时:

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

② 如果未开启Mark Word 的值为 0x01二进制后三位 001hashcode、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());
    }
}

加锁

① 直接创建然后加锁(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());
        }
    }
}

 ② 睡 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);
        synchronized (myInteger){
            log.debug(ClassLayout.parseInstance(myInteger).toPrintable());
        }
    }
}

锁撤销 

调用对象 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());
    }
}

其他线程使用该对象锁

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

@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());
        }
    }
}

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();
        }
    }
}

批量重偏向(难点)

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 个对象加的都是偏向锁 

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

 

 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();
    }
}

批量撤销(难点)

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

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

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

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

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

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();
    }
}

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

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

锁粗化 && 锁消除

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值