JAVA多线程学习-------monitor所实现原理

    科技2022-07-13  129

    1.Java对象头

    Java对象头详解

    2.monitor锁原理

    Monitor 被翻译为监视器或管程 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针 Monitor 结构如下! 同步监视器对象obj首先会向操作系统申请获取一个monitor,获取到之后会把obj对象头中的mark word中的lock字段由01改为10,并且把前30位改为monitor地址。

    刚开始 Monitor 中 Owner 为 null 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一 个 Owner 在 Thread-2 上锁的过程中,如果 Thread-1,Thread-3, 也来执行 synchronized(obj),就会进入 EntryList BLOCKED Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的 图中 WaitSet 中的 Thread-0,Thread-4 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲 wait-notify 时会分析 注意: synchronized 必须是进入同一个对象的 monitor 才有上述的效果 不加 synchronized 的对象不会关联监视器,不遵从以上规则

    3.synchronized实现原理

    1. 轻量级锁

    轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。 轻量级锁对使用者是透明的,即语法仍然是 synchronized 假设有两个方法同步块,利用同一个对象加锁

    static final Object obj = new Object(); public static void method1(){ synchronized(obj){ method2(); } } public static void method2(){ synchronized(obj){ //同步代码块 } }

    首先会进入轻量级锁流程:

    执行到method1()时,会在当前线程栈中创建一个栈帧(method1栈帧)。执行到synchronized同步代码块时,method1栈帧中的Lock Record锁记录中的地址和标志位00会和同步监视器对象obj的mark word进行CAS操作(锁记录地址和markword交换操作),当obj的markword的后两位lock位位01时,表示当前锁没有被其他线程锁占有。

    如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁

    CAS操作失败有两种情况 ① 当前线程已经拥有了当前锁。就会进入synchronized锁重入,添加一条lock reord锁记录作为重入计数。 ②其他线程已经获取了当前锁。会进入锁膨胀(下节讲述)。

    当发生锁重入时(例如执行到method2的synchronized时),会把当前栈帧的lockRecord地址赋值为null,objectReference为obj对象。

    当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重 入计数减一

    当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象 头 成功,则解锁成功 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

    2.锁膨胀

    如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有 竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

    static Object obj = new Object(); public static void method1() { synchronized( obj ) { // 同步块 } } 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

    这时 Thread-1 加轻量级锁失败,进入锁膨胀流程 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址 然后自己进入 Monitor 的 EntryList BLOCKED

    当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

    3.自旋优化

    如果线程直接进入EntryList堵塞状态,如果刚好锁owner释放锁,堵塞线程会被唤醒,成为owner,这些过程都伴随线程上下文的切换,浪费时间。 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步 块,释放了锁),这时当前线程就可以避免阻塞。 自旋重试成功的情况 自旋重试失败的情况 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会 高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。 Java 7 之后不能控制是否开启自旋功能

    Processed: 0.010, SQL: 8