synchronized原理

本文详细介绍了Java中的synchronized锁的升级机制,包括无锁、偏向锁、轻量级锁和重量级锁,以及锁消除和锁粗化的优化策略。通过代码示例展示了锁状态的变化,并讨论了何时会进行锁升级和优化,以保证线程安全和性能。
摘要由CSDN通过智能技术生成

一.锁升级

synchronized相对比较‘智能’,会根据锁竞争的激烈程度进行锁升级,其锁升级策略如下:

我们一一对一下四种上锁情况进行分析:

  1. 无锁:锁对象没有锁竞争时,synchronized并不会进行上锁操作,无锁状态是理论上会存在的状态,但是在实际业务场景中几乎不存在

  1. 偏向锁:偏向锁并不是真正的加锁,而是对锁对象进行加‘标签’的操作,这个标签记录哪个线程调用了这把锁,如果后续没有其他线程来调用这把锁,就一直保持偏向锁的状态,如果后面有其他线程同样竞争这把锁,那么只需要将标签中的线程与该线程进行对比,如果不一致,则有偏向锁转化为轻量级锁。偏向锁在本质上是延迟加锁的操作:能不加锁就不加锁,尽量避免不必要的加锁开销。

  1. 轻量级锁:当有其他线程对这把锁进行锁竞争,偏向锁的锁状态就会被解除,进入轻量级锁,在这里轻量级锁是自适应的自旋锁(通过CAS来实现):通过CAS检查并更新所资源的调用情况,如果更新成功,则加锁成功,更新失败认为锁被占用,不断进行自旋等待(并不放弃cpu),但是不断的自旋是会消耗内存,一般自旋一定的次数和时间就不再自旋了,这就是所谓的‘自适应’。

  1. 重量级锁:当锁竞争进一步加强时,轻量级锁就会转化为重量级锁,而重量级锁获取锁的过程如下:首先从用户态进入内核态,判断锁资源是否被占用,没有被占用则进行加锁操作,加锁成功后返回用户态,如果被占用该线程就进入锁的等待队列,当锁资源被释放之后,经过一些列沧海桑田(不及时),这时操作系统才考虑到被挂起的线程,这时才将该线程唤醒,进行加锁操作。

我们举生活中的一个例子对这四种锁状态进行说明:

我们通过代码对四种锁状态进行解释:

import org.openjdk.jol.info.ClassLayout;

public class Demo_404_Layout {
    // 定义一些变量
    private int count;
    private long count1 = 200;
    private String hello = "";
    // 定义一个对象变量
    private TestLayout test001 = new TestLayout();

    public static void main(String[] args) throws InterruptedException {
        // 创建一个对象的实例
        Object obj = new Object();
        // 打印实例布局
        System.out.println("=== 任意Object对象布局,起初为无锁状态");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        synchronized (obj) {
            System.out.println("==== 第一层synchronized加锁后");
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());

            System.out.println("=== 延时4S开启偏向锁");
            // 延时4S开启偏向锁
            Thread.sleep(5000);
            // 创建本类的实例
            Demo_404_Layout monitor = new Demo_404_Layout();
            // 打印实例布局,注意查看锁状态为偏向锁
            System.out.println("=== 打印实例布局,注意查看锁状态为偏向锁");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

            System.out.println("==== synchronized加锁");
            // 加锁后观察加锁信息
            synchronized (monitor) {
                System.out.println("==== 第一层synchronized加锁后");
                System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
                // 锁重入,查看锁信息
                synchronized (monitor) {
                    System.out.println("==== 第二层synchronized加锁后,锁重入");
                    System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
                }
                // 释放里层的锁
                System.out.println("==== 释放内层锁后");
                System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            }
            // 释放所有锁之后
            System.out.println("==== 释放 所有锁");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

            System.out.println("==== 多个线程参与锁竞争,观察锁状态");
            Thread thread1 = new Thread(() -> {
                synchronized (monitor) {
                    System.out.println("=== 在线程A 中获取锁,参与锁竞争,当前只有线程A 竞争锁,轻度锁竞争");
                    System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
                }
            });
            thread1.start();

            // 休眠一会,不与线程A 激烈竞争
            Thread.sleep(100);
            Thread thread2 = new Thread(() -> {
                synchronized (monitor) {
                    System.out.println("=== 在线程B 中获取锁,与其他线程进行锁竞争");
                    System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
                }
            });
            thread2.start();

            // 不休眠直接竞争锁,产生激烈竞争
            System.out.println("==== 不休眠直接竞争锁,产生激烈竞争");
            synchronized (monitor) {
                // 加锁后的类对象
                System.out.println("==== 与线程B 产生激烈的锁竞争,观察锁状态为fat lock");
                System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            }
            // 休眠一会释放锁后
            Thread.sleep(100);
            System.out.println("==== 释放锁后");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

            System.out.println("===========================================================================================");
            System.out.println("===========================================================================================");
            System.out.println("===========================================================================================");
            System.out.println("===========================================================================================");
            System.out.println("===========================================================================================");
            System.out.println("===========================================================================================");

            // 调用hashCode后才保存hashCode的值
            monitor.hashCode();
            // 调用hashCode后观察现象
            System.out.println("==== 调用hashCode后查看hashCode的值");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            // 强制执行垃圾回收
            System.gc();
            // 观察GC计数
            System.out.println("==== 调用GC后查看age的值");
            System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
            // 打印类布局,注意调用的方法不同
            System.out.println("==== 查看类布局");
            System.out.println(ClassLayout.parseClass(Demo_404_Layout.class).toPrintable());
            // 打印类对象布局
            System.out.println("==== 查看类对象布局");
            System.out.println(ClassLayout.parseInstance(Demo_404_Layout.class).toPrintable());


        }
    }
}

二.锁消除

锁消除是synchronized的一种优化策略,是编译器+JVM判断锁是否能够消除,如果能够消除就直接消除了

比如以下场景:当我们对变量进行修改时,将修改的代码块加上了synchronized,但是是在单线程的情况下,不存在线程安全问题,这时JVM直接将锁消除了

需要注意的是:只有JVM百分百确定不存在线程安全问题时,才会执行锁消除

另外一种场景:虽然JVM没办法决定在哪个地方加锁(程序员决定),但是如果是在多线程情况下的读操作,就不存在线程安全问题,所以这JVM直接将锁消除了

三.锁粗化

当在一段代码中出现多次加锁和释放锁的操作,这时候JVM会对锁自动进行锁的粗化,避免了锁的多次获取和释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

六子干侧开

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

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

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

打赏作者

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

抵扣说明:

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

余额充值