synchronized关键字的底层原理

1synchronized关键字的底层原理

Monitor

举个例子:

1.线程1执行synchronized代码块,里面用到了lock(对象锁)。首先会让这个lock对象和monitor关联,判断monitor中的owner属性是否为null。如果为null直接获取对象锁。owner只能关联一个线程。

2.现在其他线程来了,发现owner不为空,全部进入entrylist中等待。一旦线程1执行完,把锁释放了,owner又为空了,这时候entrylist中的线程就会去抢对象锁(大家共同去抢,不是先来先得!)。

3.当线程进入synchronized同步代码块并调用wait()方法时,它会释放对该对象的锁,并将自身放入与该对象关联的monitor对象的waitset中。这样,线程就进入了等待状态,等待其他线程调用notify()或notifyAll()方法将其唤醒。

2.synchronized关键字的底层原理-进阶

1.锁升级

注:一旦发生线程竞争的情况还是要用到monitor。

2.对象锁如何关联到monitor的?
  1. 对象头:这部分包含了对象的元数据信息,如对象的哈希码、分代年龄、锁标志位等。对于非数组对象,对象头通常是8字节,而对于数组对象,对象头会增加4字节用于存储数组的长度。
  2. 实例数据 :这部分包含了对象的非静态成员变量。这部分的大小取决于对象中属性的数量和类型。例如,一个int类型的属性会占用4字节,一个String类型的属性会占用更多的字节。
  3. 对齐填充:这部分是为了确保对象的总大小是8的倍数。这是因为HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。

如果使用synchronized给对象上锁(重量级)之后,对象锁的MarkWord(对象头)中的ptr_to_heavyweight_monitor就被设置指向monitor对象的地址。由此来找到需要关联的monitor。

3.轻量级锁
java 复制代码
    static final Object obj = new Object();
    public static void method1() {
        synchronized (obj) {
            // 同步块 A
            method2();//调用method2
        }
    }
    public static void method2() {
        synchronized (obj) {//同一线程重入同一把锁
        // 同步块 B
        }
    }

加锁过程

1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。

2.通过CAS指令将Lock Record的地址存储在对象头的mark word中(数据进行交 换),如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一 部分为null,起到了一个重入计数器的作用。

4.如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。

解锁过程

1.遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。

2.如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后 continue。

3.如果Lock Record的 Mark Word不为null,则利用CAS指令(保证原子性)将对象头的mark word 恢复成为无锁状态。如果失败则膨胀为重量级锁。

4.偏向锁(轻量级锁的优化)

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

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

java 复制代码
    static final Object obj = new Object();
    public static void m1() {
        synchronized (obj) {//第一次获取锁
        // 同步块 A
            m2();
        }
    }
    public static void m2() {
        synchronized (obj) {//第一次重入锁
        // 同步块 B
            m3();
        }
    }
    public static void m3() {
        synchronized (obj) {//第二次重入锁

        }
    }

多次重入锁,可以用偏向锁提升性能。

加锁的流程

1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。

2.通过CAS指令将Lock Record的线程id存储在对象头的mark word中,同时也设 置偏向锁的标识为101,如果对象处于无锁状态则修改成功,代表该线程获得了 偏向锁。

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一 部分为null,起到了一个重入计数器的作用。与轻量级锁不同的时,这里不会再 次进行cas操作,只是判断对象头中的线程id是否是自己,因为缺少了cas操作, 性能相对轻量级锁更好一些。

解锁流程参考轻量级锁

偏向锁只会在第一次添加锁的时候进行CAS操作,之后每一次重入只是判断当前锁是不是当前线程的。而轻量级锁每次重入都会执行一次CAS操作。因此偏向锁性能更好一点。

5.小结
相关推荐
忘忧人生4 分钟前
docker 部署 java 项目详解
java·docker·容器
查理零世5 分钟前
【算法】数论基础——约数个数定理、约数和定理 python
python·算法·数论
null or notnull32 分钟前
idea对jar包内容进行反编译
java·ide·intellij-idea·jar
汉克老师2 小时前
GESP2024年3月认证C++六级( 第三部分编程题(1)游戏)
c++·学习·算法·游戏·动态规划·gesp6级
闻缺陷则喜何志丹2 小时前
【C++图论】2685. 统计完全连通分量的数量|1769
c++·算法·力扣·图论·数量·完全·连通分量
言午coding2 小时前
【性能优化专题系列】利用CompletableFuture优化多接口调用场景下的性能
java·性能优化
利刃大大2 小时前
【二叉树深搜】二叉搜索树中第K小的元素 && 二叉树的所有路径
c++·算法·二叉树·深度优先·dfs
CaptainDrake2 小时前
力扣 Hot 100 题解 (js版)更新ing
javascript·算法·leetcode
牛马程序员‍2 小时前
Day104 JVM 原理及优化
jvm
一缕叶2 小时前
洛谷P9420 [蓝桥杯 2023 国 B] 子 2023 / 双子数
算法·蓝桥杯