偏向锁-多线程安全-并发编程(Java)

本文深入探讨Java中的偏向锁机制,包括其引入原因、工作原理、状态转换、撤销条件以及批量重定向和消除。通过实例代码展示了偏向锁在不同场景下的行为,如线程竞争、hashcode调用、多线程访问等,揭示了JVM如何在无锁竞争时提升性能。
摘要由CSDN通过智能技术生成

1、偏向锁引入

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行cas操作。

Java 6引入偏向锁进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只有不发生竞争,这个对象就归改线程所有。

2、偏向状态

64位Java对象头Mark Word结构:

Mark Word(64 bits)State
unused:25hashcode:31unused:1age:4biased_lock:001Normal
thread:54epoch:2unused:1age:4biased_lock:101Biased
ptr_to_lock_record:6200LightWight Locked
ptr_to_heavyweight_monitor:6210HeavyWeight Locked
 11Marked for GC

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建成功后,markword值为0x05即最后3位为101,这是它的thread、epoch、age都为0

  • 偏向锁模式是延迟的,不会在程序启动时立即生效。如果想立即生效,可以加VM参数:-XX:BiasedLockingStartupDelay=0来禁用延迟

    // 已添加禁用延迟-XX:BiasedLockingStartupDelay=0
    public class Person {
    }
    @Slf4j(topic = "security.lock.TestBiased01")
    public class TestBiased01 {
        public static void main(String[] args) throws InterruptedException {
            Person p = new Person();
            log.debug(ClassLayout.parseInstance(p).toPrintable());
            synchronized (p) {
                log.debug(ClassLayout.parseInstance(p).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(p).toPrintable());
        }
    }
    // 测试结果
    OFF  SZ   TYPE DESCRIPTION               VALUE
      0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
      0   8        (object header: mark)     0x0000000002198805 (biased: 0x0000000000008662; epoch: 0; age: 0)
      0   8        (object header: mark)     0x0000000002198805 (biased: 0x0000000000008662; epoch: 0; age: 0)
    
  • 如果没有开启偏向锁,那么对象创建后,markword值为0x01即后3位为001,这是它的hashcode、age都为0,第一用到hashcode即调用object.hashcode()时才会赋值。

    • 添加VM参数:-XX:-UserBiasedLocking ,禁用偏向锁。

3、偏向锁撤销

3.1、hashcode

调用了对象的hashcode,但偏向锁的对象中存储的是线程id,如果调用hashcode会导致偏向锁被撤销

  • 轻量级锁会在锁记录中记录hashCode
  • 重量级锁会在Monitor中记录hashCode

在调用hashCode后使用偏向锁,需要设置VM参数:-XX:UseBiasedLocking

//在上面的测试中,创建对象之后,调用
System.out.println("hashcode: " + p.hashCode());
// 测试结果
hashcode: 1626877848
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00000060f82f9801 (hash: 0x60f82f98; age: 0)
  0   8        (object header: mark)     0x000000000252f848 (thin lock: 0x000000000252f848)
  0   8        (object header: mark)     0x00000060f82f9801 (hash: 0x60f82f98; age: 0)	

3.2、多线程(不发生竞争)

当其他线程使用偏向锁对象时(不发生竞争),会将偏向锁升级为轻量级锁。

@Slf4j(topic = "s.RevokeBiasedLock")
public class RevokeBiasedLock {
    public static void main(String[] args) {
        Person p = new Person();
         new Thread(() -> {
            log.debug(ClassLayout.parseInstance(p).toPrintable());
            synchronized (p) {
                log.debug(ClassLayout.parseInstance(p).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(p).toPrintable());
            synchronized (RevokeBiasedLock.class) {
                RevokeBiasedLock.class.notify();
            }
        }, "t1").start();

       new Thread(() -> {
            synchronized (RevokeBiasedLock.class) {
                try {
                    RevokeBiasedLock.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug(ClassLayout.parseInstance(p).toPrintable());
            synchronized (p) {
                log.debug(ClassLayout.parseInstance(p).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(p).toPrintable());
        }, "t2").start();
    }
}
// 测试结果	
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  0   8        (object header: mark)     0x000000001e551805 (biased: 0x0000000000079546; epoch: 0; age: 0)
  0   8        (object header: mark)     0x000000001e551805 (biased: 0x0000000000079546; epoch: 0; age: 0)
  0   8        (object header: mark)     0x000000001e551805 (biased: 0x0000000000079546; epoch: 0; age: 0)
  0   8        (object header: mark)     0x000000001ef6f190 (thin lock: 0x000000001ef6f190)
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  

3.3、wait-notify

一旦调用wait-notify,都会升级为重量级锁,后面wait-notify在详细介绍。

4、批量重定向

如果对象虽然被多个线程访问,单没有竞争,只是偏向了线程t1的对象仍然有可能重新偏向t2,重偏向对重置对象的Thread ID

当撤销偏向锁阈值超过20次后,jvm会在给这些对象加锁时,重新偏向至加锁线程。

@Slf4j(topic = "s.BatchReBias")
public class BatchReBias {
    public static void main(String[] args) {
        Vector<Person> list = new Vector<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Person p = new Person();
                list.add(p);
                synchronized (p) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                }
            }
            synchronized (list) {
                list.notify();
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(() -> {
            synchronized (list) {
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            for (int i = 0; i < 30; i++) {
                Person p  = list.get(i);
                log.debug("==================");
                log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                synchronized (p) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
            }

        }, "t2");
        t2.start();
    }
}
// 测试结果
t1 1
OFF  SZ   TYPE DESCRIPTION               VALUE
0   8        (object header: mark)     0x000000001de4e805 (biased: 0x000000000007793a; epoch: 0; age: 0)
...
t2 1
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
0   8        (object header: mark)     0x000000001ec8f0c0 (thin lock: 0x000000001ec8f0c0)
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
...
t2 20
0   8        (object header: mark)     0x000000001de4e805 (biased: 0x000000000007793a; epoch: 0; age: 0)0   8        (object header: mark)     0x000000001de4f905 (biased: 0x000000000007793e; epoch: 0; age: 0)
0   8        (object header: mark)     0x000000001de4f905 (biased: 0x000000000007793e; epoch: 0; age: 0)
0   8        (object header: mark)     0x000000001de4f905 (biased: 0x000000000007793e; epoch: 0; age: 0)
...

5、批量撤销

当撤销偏向锁阈值超过40次后,整个类的所有对象都会变为不可偏向,新建的对象也是不可偏向的。

@Slf4j(topic = "s.BatchRevoke")
public class BatchRevoke {
    static Thread t1, t2, t3;
    public static void main(String[] args) throws InterruptedException {
        Vector<Person> list = new Vector<>();
        int num = 39;
        t1 = new Thread(() -> {
            for (int i = 0; i < num; i++) {
                Person p = new Person();
                list.add(p);
                synchronized (p) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                }
            }
            LockSupport.unpark(t2);
        }, "t1");
        t1.start();
        t2 = new Thread(() -> {
            LockSupport.park();
            for (int i = 0; i < num; i++) {
                Person p  = list.get(i);
                log.debug("==================");
                log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                synchronized (p) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
            }
            LockSupport.unpark(t3);

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

        t3 = new Thread(() -> {
            LockSupport.park();
            log.debug("==================>");
            for (int i = 0; i < num; i++) {
                Person p  = list.get(i);
                log.debug("==================");
                log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                synchronized (p) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(p).toPrintable());
            }

        }, "t3");
        t3.start();
        t3.join();
        log.debug(ClassLayout.parseInstance(new Person()).toPrintable());
    }
}
// 测试结果
t1 1
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001e2e7805 (biased: 0x0000000000078b9e; epoch: 0; age: 0)
...
t2 1
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001e2e7805 (biased: 0x0000000000078b9e; epoch: 0; age: 0)
  0   8        (object header: mark)     0x000000001e07f2a8 (thin lock: 0x000000001e07f2a8)
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
...
t2 20
0   8        (object header: mark)     0x000000001e2e7805 (biased: 0x0000000000078b9e; epoch: 0; age: 0)
0   8        (object header: mark)     0x000000001e2ea905 (biased: 0x0000000000078baa; epoch: 0; age: 0)
0   8        (object header: mark)     0x000000001e2ea905 (biased: 0x0000000000078baa; epoch: 0; age: 0)
...
t3 1
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
0   8        (object header: mark)     0x000000001ee6eeb8 (thin lock: 0x000000001ee6eeb8)
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
主线程 新创建对象
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)

6、锁消除

@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
    static int x = 0;
    @Benchmark
    public void a() throws Exception {
        x++;
    }
    @Benchmark
    // JIT  即时编译器
    public void b() throws Exception {
        Object o = new Object();
        synchronized (o) {
            x++;
        }
    }
}
// 测试结果
Benchmark            Mode  Samples  Score  Score error  Units
c.i.MyBenchmark.a    avgt        5  2.256        0.046  ns/op
c.i.MyBenchmark.b    avgt        5  2.239        0.036  ns/op
  • BenchmarkMode(Mode.AvrageTime):模式-计算平均时间
  • @Warmup(iterations = 3):热身-3次
  • @Measurement(iterations = 5):测试-5次
  • @OutputTimeUnit(TimeUnit.NANOSECONDS):计量单位-纳秒

JIT即时编辑器,对热点代码的java字节码优化。

Object o = new Object(); 对象o为局部变量,加锁没有效果,此时,JIT会对synchronzied优化,实际执行并不会有synchronized加锁操作,所有性能接近。

  • VM 参数 -XX:-EliminateLocks 控制开关,关闭不会执行JIT优化,在此查看结果:

    Benchmark            Mode  Samples   Score  Score error  Units
    c.i.MyBenchmark.a    avgt        5   2.209        0.034  ns/op
    c.i.MyBenchmark.b    avgt        5  21.753        0.580  ns/op
    
    

性能差距明显。

仓库地址:https://gitee.com/gaogzhen/concurrent

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值