3.JUC【Java面试第三季】

news2024/9/30 23:36:52

3.JUC【Java面试第三季】

  • 前言
  • 推荐
  • 3.JUC
  • 06_闲聊AQS面试
    • 1.题目+说明
  • 07_可重入锁理论
    • 2.可重入锁
      • 说明
      • “可重入锁”这四个字分开来解释
      • 可重入锁的种类
  • 08_可重入锁的代码验证-上
  • 09_可重入锁的代码验证-下
    • 3.LockSupport
  • 10_LockSupport是什么
      • LockSupport是什么
  • 11_waitNotify限制
      • ==线程等待唤醒机制(wait/notify)==
        • 3种让线程等待和唤醒的方法
        • Object类中的wait和notify方法实现线程等待和唤醒
          • 代码
          • 小总结
  • 12_awaitSignal限制
        • Condition接口中的await后signal方法实现线程的等待和唤醒
          • 代码
          • 小总结
        • ==传统的synchronized和lLock实现等待唤醒通知的约束==
  • 13_LockSupport方法介绍
        • LockSupport类中的park等待和unpark唤醒
          • 是什么
          • 主要方法
  • 14_LockSupport案例解析
          • 代码
          • 重点说明(重要)
          • 面试题
  • 15_AQS理论初步
    • 4.AbstractQueuedSynchronizer之AQS
      • 先从字节跳动及其它大厂面试题说起前置知识
      • 前置知识
      • 是什么
  • 16_AQS能干嘛
      • AQS为什么是JUC内容中最重要的基石
        • 和AQS有关的
        • 进一步理解锁和同步器的关系
      • 能干嘛
  • 17_AQS源码体系-上
      • AQS初步
        • AQS初识
  • 18_AQS源码体系-下
        • AQS内部体系架构
          • AQS自身
          • AbstractQueuedSynchronizer内部类
        • AQS同步队列的基本结构
  • 19_AQS源码深度解读01
      • 从我们的ReentrantLock开始解读AQS
        • Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的
        • ReentrantLock的原理
        • 从最简单的lock方法开始看看公平和非公平
        • 非公平锁走起,方法lock()
  • 20_AQS源码深度解读02
  • 21_AQS源码深度解读03
  • 22_AQS源码深度解读04
  • 23_AQS源码深度解读05
  • 24_AQS源码深度解读06
  • 25_AQS源码深度解读07
        • 方法unlock()
  • 26_AQS小总结
  • 最后

前言

2023-2-1 14:57:23

以下内容源自
【尚硅谷Java大厂面试题第3季,跳槽必刷题目+必扫技术盲点(周阳主讲)-哔哩哔哩】
仅供学习交流使用

推荐

Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)

3.JUC

06_闲聊AQS面试

1.题目+说明

0353

07_可重入锁理论

2.可重入锁

说明

可重入锁又名递归锁

是指在同一个线程在外层方法获取锁的时候,再进入该线程的的内层方法会自动获取锁(前提是锁对象得是同一个对象)
不会因为之前已经获取过还没释放而阻塞。

Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

“可重入锁”这四个字分开来解释

  • 可:可以

  • 重:再次

  • 入:进入

  • 锁:同步锁

  • 进入什么?

    • 进入同步域(即同步代码块/方法或显示锁锁定的代码)
  • 一句话

    • 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。
    • 自己可以获取自己的内部锁。

可重入锁的种类

  • 隐式锁(即synchronized关键字使用的锁)默认是可重入锁。
    • 同步块
    • 同步方法

08_可重入锁的代码验证-上

同步代码块

package juc;

/**
 * 可重入锁;可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
 *
 * 在一个synchronized修饰的方法或代码块的内部
 * 调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的
 */
public class ReEnterLockDemo {
    static Object objectA=new Object();

    public static void m1() {
        new Thread(()->{
            synchronized (objectA){
                System.out.println(Thread.currentThread().getName()+"\t"+"-----外层调用");
                synchronized (objectA){
                    System.out.println(Thread.currentThread().getName()+"\t"+"-----中层调用");
                    synchronized (objectA){
                        System.out.println(Thread.currentThread().getName()+"\t"+"-----内层调用");
                    }
                }
            }
        },"t1").start();
    }
    public static void main(String[] args) {
        m1();
    }
}

输出结果:

t1	-----外层调用
t1	-----中层调用
t1	-----内层调用

09_可重入锁的代码验证-下

同步方法

package juc;

/**
 * 可重入锁;可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
 *
 * 在一个synchronized修饰的方法或代码块的内部
 * 调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的
 */
public class ReEnterLockDemo {
   
    public synchronized void m1() {
        System.out.println("=====外");
        m2();
    }
    public synchronized void m2(){
        System.out.println("=====中");
        m3();
    }
    public synchronized void m3(){
        System.out.println("=====内");

    }
    public static void main(String[] args) {
        new ReEnterLockDemo().m1();
    }
}

  • Synchronized的重入的实现机理。

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。

当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

  • 显式锁(即Lock)也有ReentrantLock这样的可重入锁。
package juc;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的
 */
public class ReEnterLockDemo {

    static Lock lock=new ReentrantLock();

    public static void main(String[] args) {
       new Thread(()->{
           lock.lock();
           lock.lock();
           try {
               System.out.println("======外层");
               lock.lock();
               try {
                   System.out.println("======内层");
               } finally {
                   lock.unlock();
               }
           } finally {
               //这里故意注释,实现加锁次数和释放次数不一样
               //由于加锁次数和释放次数不一样,第二个线程始终无法得到锁,导致一直在等待
               lock.unlock();
               lock.unlock();//正常情况,加锁几次就要解锁几次
           }
       },"t1").start();

        new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"\t"+"-----调用开始");
            } finally {
                lock.unlock();
            }
        },"t2").start();
    }
}

3.LockSupport

10_LockSupport是什么

1 为什么要学习LockSupport?
	1.1 Java----JVM
	1.2 JUC----AQS---->(前置知识可重入锁、 LockSupport)
2学习方法
	2.1是什么?
	2.2能干嘛
	2.3去哪下
	2.4怎么玩
3 AB----》after [ before
synchronized-wait-notify
lock-await-signal
LockSupport-park-unpark

LockSupport是什么

LockSupport Java doc

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport中的park()和 unpark()的作用分别是阻塞线程和解除阻塞线程。

总之,比wait/notify,await/signal更强。

Class LockSupport

0522

  • 是什么

0650

11_waitNotify限制

线程等待唤醒机制(wait/notify)

3种让线程等待和唤醒的方法

  • 方式1:使用Object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程
  • 方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
  • 方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

Object类中的wait和notify方法实现线程等待和唤醒

代码
  • 正常
package juc;

public class LockSupportDemo {
    static Object objectLock =new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName()+"\t"+"------come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+"------被唤醒");
            }
        },"A").start();
        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"\t"+"------通知");
            }
        },"B").start();
    }
}

  • 异常1
package juc;



/**
 * 要求:t1线程等待3秒钟,3秒钟后t2线程唤龌t1线程继续工作
 * 以下异常情况:
 * 2wait方法和notify方法,两个都去掉同步代码块后看运行效果
 *  2.1 异常情况
 *  Exception in thread "t1" java.lang.ILLegalMonitorStateException at java.Lang.Object.wait(Native Method)
 *  Exception in thread "t2" java.Lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method)
 *  2.2 结论
 *  Object类中的wait、notify、notifyALL用于线程等待和唤醒的方法,都必须在synchronized内部执行(必须用到关键字synchronized)。
 */
public class LockSupportDemo_2 {
    static Object objectLock =new Object();

    public static void main(String[] args) {
        new Thread(()->{
//            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName()+"\t"+"------come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+"------被唤醒");
//            }
        },"A").start();
        new Thread(()->{
//            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"\t"+"------通知");
//            }
        },"B").start();
    }
}


wait方法和notify方法,两个都去掉同步代码块

  • 异常情况
A	------come in
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at juc.LockSupportDemo_2.lambda$main$1(LockSupportDemo_2.java:22)
	at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at juc.LockSupportDemo_2.lambda$main$0(LockSupportDemo_2.java:13)
	at java.lang.Thread.run(Thread.java:745)

Process finished with exit code 0

  • 异常2
package juc;

import java.util.concurrent.TimeUnit;

/**
 * 要求:t1线程等待3秒钟,3秒钟后t2线程唤龌t1线程继续工作
 *
 * 3 将notify放在wait方法前先执行,t1先notify了,3秒钟后t2线程再执行ait方法
 *  3.1程序一直无法结桑1
 *  3.2结论
 * 先wait后notify、notifyalL方法,等待中的线程才会被唤醒,否则无法唤醒
 */
public class LockSupportDemo_3 {
    static Object objectLock =new Object();

    public static void main(String[] args) {
        new Thread(()->{
            //暂停几秒钟线程
            try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName()+"\t"+"------come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+"------被唤醒");
            }
        },"A").start();
        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"\t"+"------通知");
            }
        },"B").start();
    }
}

将notify放在wait方法前面
程序无法热行,无法唤醒

小总结

wait和notify方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException。

调用顺序要先wait后notify才OK。

12_awaitSignal限制

Condition接口中的await后signal方法实现线程的等待和唤醒

Condition接口中的await后signal方法实现线程的等待和唤醒,与Object类中的wait和notify方法实现线程等待和唤醒类似。

代码
  • 正常
package juc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo_2_1 {
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+"-----被唤醒");
            } finally {
                lock.unlock();
            }

        }, "A").start();

        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
            } finally {
                lock.unlock();
            }

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

  • 异常1
package juc;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo_2_2 {
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        new Thread(() -> {
//            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+"-----被唤醒");
            } finally {
//                lock.unlock();
            }

        }, "A").start();

        new Thread(() -> {
//            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
            } finally {
//                lock.unlock();
            }

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

  • 异常2
package juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo_2_3 {
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        new Thread(() -> {
            //暂停几秒钟线程
            try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+"-----被唤醒");
            } finally {
                lock.unlock();
            }

        }, "A").start();

        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t" + "------通知");
            } finally {
                lock.unlock();
            }

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

小总结

await和signal方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException。

调用顺序要先await后signal才OK。

传统的synchronized和lLock实现等待唤醒通知的约束

  • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒 ,线程才能够被唤醒

13_LockSupport方法介绍

LockSupport类中的park等待和unpark唤醒

是什么
  • 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
  • 官网解释
    0207
主要方法
  • API
    0336

  • 阻塞

    • park() /park(Object blocker)
      0346

    • 阻塞当前线程/阻塞传入的具体线程

  • 唤醒

    • unpark(Thread thread)
      0457

    • 唤醒处于阻塞状态的指定线程

14_LockSupport案例解析

代码
  • 正常+无锁块要求
package juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo_3_1 {
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread a = new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
            LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证
            System.out.println(Thread.currentThread().getName() + "\t" + "-----被唤醒");

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

        //暂停几秒钟线程
        try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }

        Thread b =new Thread(() -> {
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName() + "\t" + "------通知");

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

  • 之前错误的先唤醒后等待,LockSupport照样支持
package juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo_3_2 {
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            //暂停几秒钟线程
            try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }

            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in"+System.currentTimeMillis());
            LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证      //1675241932025--没起作用--1675241932025
            System.out.println(Thread.currentThread().getName() + "\t" + "-----被唤醒"+System.currentTimeMillis());

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


        Thread b =new Thread(() -> {
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName() + "\t" + "------通知");

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


  • 解释
    sleep方法3秒后醒来,执行park无效,没有阻塞效果,解释如下先执行了unpark(t1)导致上面的park方法形同虚设无效,时间一样
重点说明(重要)

LockSupport是用来创建锁和共他同步类的基本线程阻塞原语。

LockSuport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻寨之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。

LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程

LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,

调用一次unpark就加1变成1,

调用一次park会消费permit,也就是将1变成0,同时park立即返回。

如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。

形象的理解

线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。

当调用park方法时

  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出。

  • 如果无凭证,就必须阻塞等待凭证可用。

而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无放。

面试题

为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。

为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1(不能累加),连续调用两次 unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。

package juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

public class LockSupportDemo_3_3 {
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread a = new Thread(() -> {
            //暂停几秒钟线程
            try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }

            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in"+System.currentTimeMillis());
            LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证           //1675241932025--没起作用--1675241932025
            LockSupport.park();//被阻塞...等待通知等待放行,它要通过需要许可证           //1675241932025--没起作用--1675241932025
            System.out.println(Thread.currentThread().getName() + "\t" + "-----被唤醒"+System.currentTimeMillis());

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


        Thread b =new Thread(() -> {
            LockSupport.unpark(a);
            LockSupport.unpark(a);//最多一个凭证
            System.out.println(Thread.currentThread().getName() + "\t" + "------通知");

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

15_AQS理论初步

4.AbstractQueuedSynchronizer之AQS

先从字节跳动及其它大厂面试题说起前置知识

  • 同学反馈2020.6.27
    【Java集合类】
    1、从集合开始吧,介绍一下常用的集合类,哪些是有序的,哪些是无序的
    2、hashmap是如何寻址的,哈希碰撞后是如何存储数据的,1.8后什么时候变成红黑树、说下红黑树的理解,红黑树有什么好处
    3、concurrrenthashmap 怎么实现线程安全,一个里面会有几个段 segment,jdk1.8后有优化concurrenthashmap吗?分段锁有什么坏处
    【多线程JUC】
    4、reentrantlock实现原理,简单说下aqs
    5、synchronized实现原理,monitor对象什么时候生成的?知道monitor的monitorenter和moni这两个是怎么保证同步的吗,或者说,这两个操作讦算机底层是如何执行的
    6、刚刚你提到了synchronized的优化过程,详细说一下吧。偏向锁和轻量级锁有什么区别?
    7、线程池几个参数说下,你们项目中如何根据实际场景设詈参数的,为什么cpu密集设置的线程密集型少

前置知识

  • 公平锁和非公平锁
  • 可重入锁
  • LockSupport
  • 自旋锁
  • 数据结构之链表
  • 设计模式之模板设计模式

是什么

  • 字面意思

    • 抽象的队列同步器
    • 源代码
      0424
  • 技术解释
    是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。

0933
CLH:Craig、Landin and Hagersten队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO。

16_AQS能干嘛

AQS为什么是JUC内容中最重要的基石

和AQS有关的

0227

  • ReentrantLock
    0111

  • CountDownLatch
    0155

  • ReentrantReadWriteLock
    0213

  • Semaphore
    0218

进一步理解锁和同步器的关系

  • 锁,面向锁的 使用者 ----定义了程序员和锁交互的使用层APl,隐藏了实现细节,你调用即可

  • 同步器,面向锁的 实现者 -----比如Java并发大神DougLee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。

能干嘛

  • 加锁会导致阻塞

    • 有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
  • 解释说明

抢到资源的线程直接使用处理业务逻辑,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。

既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node),通过CAS、自旋以及LockSupport.park() 的方式,维护state变量的状态,使并发达到同步的控制效果。

0946

17_AQS源码体系-上

AQS初步

AQS初识

  • 官网解释
    在这里插入图片描述

  • 有阻塞就需要排队,实现排队必然需要队列

    • AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFo队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node,节点来实现锁的分配,通过CAS完成对State值的修改。
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    private static final long serialVersionUID = 7373984972572414691L;

     * Creates a new {@code AbstractQueuedSynchronizer} instance
    protected AbstractQueuedSynchronizer() { }

     * Wait queue node class.
    static final class Node {

     * Head of the wait queue, lazily initialized.  Except for
    private transient volatile Node head;

     * Tail of the wait queue, lazily initialized.  Modified only via
    private transient volatile Node tail;

     * The synchronization state.
    private volatile int state;

     * Returns the current value of synchronization state.
    protected final int getState() {

     * Sets the value of synchronization state.
    protected final void setState(int newState) {

     * Atomically sets synchronization state to the given updated
    protected final boolean compareAndSetState(int expect, int update) {
         
    ...
}         

0052

18_AQS源码体系-下

AQS内部体系架构

AQS自身
  • AQS的int变量

    • AQS的同步状态state成员变量
      在这里插入图片描述

    • state成员变量相当于银行办理业务的受理窗口状态。

      • 零就是没人,自由状态可以办理

      • 大于等于1,有人占用窗口,等着去

  • AQS的CLH队列

    • CLH队列(三个大牛的名字组成),为一个双向队列
      0322

    • 银行候客区的等待顾客

  • 小总结

    • 有阻塞就需要排队,实现排队必然需要队列
    • state变量+CLH变种的双端队列
AbstractQueuedSynchronizer内部类
  • Node的int变量

    • Node的等待状态waitState成员变量
      在这里插入图片描述
    • 说人话
      • 等候区其它顾客(其它线程)的等待状态队列中每个排队的个体就是一个Node
  • Node此类的讲解

    • 内部结构
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    ...

     * Creates a new {@code AbstractQueuedSynchronizer} instance
    protected AbstractQueuedSynchronizer() { }

     * Wait queue node class.
    static final class Node {
        //表示线程以共享的模式等待锁
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        
        //表示线程正在以独占的方式等待锁
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        //线程被取消了
        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;

        //后继线程需要唤醒
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        
        //等待condition唤醒
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        
        //共享式同步状态获取将会无条件地传播下去
        * waitStatus value to indicate the next acquireShared should     
        static final int PROPAGATE = -3;

        //当前节点在队列中的状态(重点)
        //说人话:
        //等候区其它顾客(其它线程)的等待状态
        //队列中每个排队的个体就是一个Node
        //初始为0,状态上面的几种
         * Status field, taking on only the values:
        volatile int waitStatus;

        //前驱节点(重点)
         * Link to predecessor node that current node/thread relies on
        volatile Node prev;

        //后继节点(重点)
         * Link to the successor node that the current node/thread
        volatile Node next;

        //表示处于该节点的线程
         * The thread that enqueued this node.  Initialized on
        volatile Thread thread;

        //指向下一个处于CONDITION状态的节点
         * Link to next node waiting on condition, or the special
        Node nextWaiter;

         * Returns true if node is waiting in shared mode.
        final boolean isShared() {

        //返回前驱节点,没有的话抛出npe
         * Returns previous node, or throws NullPointerException if null.
        final Node predecessor() throws NullPointerException {

        Node() {    // Used to establish initial head or SHARED marker

        Node(Thread thread, Node mode) {     // Used by addWaiter

        Node(Thread thread, int waitStatus) { // Used by Condition
    }
	...
}

  • 属性说明
    0935

AQS同步队列的基本结构

1028

19_AQS源码深度解读01

从我们的ReentrantLock开始解读AQS

Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;
    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
    }
    public void lock() {
        sync.lock();
    }
    public void unlock() {
        sync.release(1);
    }

ReentrantLock的原理

0352

从最简单的lock方法开始看看公平和非公平

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

0508
0545

0605

可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()
hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法

	//公平锁加锁时判断等待队列中是否存在有效节点的方法
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
        //  h != t 等待队列非空
        // (s = h.next) == null  队首是否为空
        // s.thread != Thread.currentThread()) 队首是否是当前线程
        // 判断等待队列是否是空或等待队列队首是否是当前线程
        
    }

非公平锁走起,方法lock()

对比公平锁和非公平锁的tyAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断!hasQueuedPredecessors()

hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;

非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(),之后还是需要竞争锁(存在线程竞争的情况下)

0938

  • 本次讲解我们走最常用的,lock/unlock作为案例突破口
  • 源码解读比较困难,别着急—阳哥的全系列脑图给大家做好笔记
  • AQS源码深度分析走起

整个 ReentrantLock的加锁过程,可以分为三个阶段:
1、尝试加锁;
2、加锁失败,线程入队列;
3、线程入队列后,进入阻塞状态。
对应下面①②③三部分。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

20_AQS源码深度解读02

ReentrantLock的示例程序

带入一个银行办理业务的案例来模拟我们的AQS 如何进行线程的管理和通知唤醒机制,3个线程模拟3个来银行网点,受理窗口办理业务的顾客。

package juc;

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;


public class AQSDemo {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        //带入一个银行办理业务的案例来模拟我们的AQs 如何进行线程的管理和通知唤醒机制\
        
        //3个线程模拟3个来银行网点,受理窗口办理业务的顾客

        //A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
        new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " come in.");

                try {
                    TimeUnit.SECONDS.sleep(5);//模拟办理业务时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }
        }, "Thread A").start();

        //第2个顾客,第2个线程---->,由于受理业务的窗口只有一个(只能一个线程持有锁),此代B只能等待,
        //进入候客区
        new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " come in.");

            } finally {
                lock.unlock();
            }
        }, "Thread B").start();


        //第3个顾客,第3个线程---->,由于受理业务的窗口只有一个(只能一个线程持有锁),此代C只能等待,
        //进入候客区
        new Thread(()->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " come in.");

            } finally {
                lock.unlock();
            }
        }, "Thread C").start();
    }
}


程序初始状态方便理解图
0354

  • lock()
    1050

  • acquire()

  • tryAcquire(arg)

  • addWaiter(Node.EXCLUSIVE)

  • acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

启动程序,首先是运行线程A,ReentrantLock默认是选用非公平锁。

public class ReentrantLock implements Lock, java.io.Serializable {
    
    ...
        
    * Acquires the lock.
    public void lock() {
        sync.lock();//<------------------------注意,我们从这里入手,一开始将线程A的
    }
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        
        ...

        //被NonfairSync的tryAcquire()调用
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        ...

    }
    
    
	//非公平锁
	static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {//<----线程A的lock.lock()调用该方法
            if (compareAndSetState(0, 1))//AbstractQueuedSynchronizer的方法,刚开始这方法返回true
                setExclusiveOwnerThread(Thread.currentThread());//设置独占的所有者线程,显然一开始是线程A
            else
                acquire(1);//稍后紧接着的线程B将会调用该方法。
        }

        //acquire()将会间接调用该方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);//调用父类Sync的nonfairTryAcquire()
        }
        

        
    }
    
    ...
}

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    /**
     * The synchronization state.
     */
    private volatile int state;

    //线程A将state设为1,下图红色椭圆区
    /*Atomically sets synchronization state to the given updated value 
    if the current state value equals the expected value.
    This operation has memory semantics of a volatile read and write.*/
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

}

线程A开始办业务了。

0847

B将会等候

acquire(1);//稍后紧接着的线程B将会调用该方法。

调用acquire方法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

21_AQS源码深度解读03

轮到线程B运行

public class ReentrantLock implements Lock, java.io.Serializable {
    
    ...
        
    * Acquires the lock.
    public void lock() {
        sync.lock();//<------------------------注意,我们从这里入手,线程B的执行这
    }
    
	//非公平锁
	static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {//<-------------------------线程B的lock.lock()调用该方法
            if (compareAndSetState(0, 1))//这是预定线程A还在工作,这里返回false
                setExclusiveOwnerThread(Thread.currentThread());//
            else
                acquire(1);//线程B将会调用该方法,该方法在AbstractQueuedSynchronizer,
            			   //它会调用本类的tryAcquire()方法
        }

        //acquire()将会间接调用该方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);//调用父类Sync的nonfairTryAcquire()
        }
    }

    //非公平锁与公平锁的公共父类
     * Base of synchronization control for this lock. Subclassed
    abstract static class Sync extends AbstractQueuedSynchronizer {
    
        //acquire()将会间接调用该方法  但是返回值会取反
    	...
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//这里是线程B
            int c = getState();//线程A还在工作,c=>1
            //这种情况就是持有锁的线程A刚刚释放锁了,等待线程B就可以占用锁了
            if (c == 0) {//false
                if (compareAndSetState(0, acquires)) { 
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //这种情况就是持有锁的线程,还想占有锁 可重入锁
            else if (current == getExclusiveOwnerThread()) {//(线程B == 线程A) => false  
                int nextc = c + acquires;//+1    
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;//最终返回false
        } 
        ...
    
    }
    
    ...
}

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	...

     * Acquires in exclusive mode, ignoring interrupts.  Implemented
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//线程B调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//下一节论述 Node.EXCLUSIVE排他的
            selfInterrupt();
    }
    
    ...
}

另外

假设线程B,C还没启动,正在工作线程A重新尝试获得锁,也就是调用lock.lock()多一次

    //非公平锁与公平锁的公共父类fa
     * Base of synchronization control for this lock. Subclassed
    abstract static class Sync extends AbstractQueuedSynchronizer {
    
    	...
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//这里是线程A
            int c = getState();//线程A还在工作,c=>1;如果线程A恰好运行到在这工作完了,c=>0,这时它又要申请锁的话
            if (c == 0) {//线程A正在工作为false;如果线程A恰好工作完,c=>0,这时它又要申请锁的话,则为true
                if (compareAndSetState(0, acquires)) {//线程A重新获得锁
                    setExclusiveOwnerThread(current);//这里相当于NonfairSync.lock[添加链接描述](https://www.bilibili.com/video/BV1Hy4y1B78T?p=23)()另一重设置吧!
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//(线程A == 线程A) => true
                int nextc = c + acquires;//1+1=>nextc=2
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//state=2,说明要unlock多两次吧(现在盲猜)
                return true;//返回true
            }
            return false;
        } 
        ...
    
    }

22_AQS源码深度解读04

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	...

     * Acquires in exclusive mode, ignoring interrupts.  Implemented
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//线程B调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//线程B加入等待队列
            selfInterrupt();//下一节论述
    }
    
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode); //此时是线程B 模式是排他 此时等候队列无结点
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {//根据上面一句注释,本语句块的意义是将新节点快速添加至队尾
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);//快速添加至队尾失败,则用这方法调用(可能链表为空,才调用该方法)
        return node;
    }
    
    //Inserts node into queue, initializing if necessary.
    private Node enq(final Node node) {
        for (;;) { 
            Node t = tail;//t=上一次循环的尾指针也就是虚拟头结点
            if (t == null) { // Must initialize 此时尾指针为空
                if (compareAndSetHead(new Node()))//插入一个哨兵节点(或称傀儡节点) 头指针-->虚拟头结点
                    tail = head; //尾指针-->虚拟头结点
            } else { //循环第二次走以下代码
                node.prev = t; //虚拟头结点t<---B
                if (compareAndSetTail(t, node)) {//真正插入我们需要的节点,也就是包含线程B引用的节点 尾指针指向tail--->B结点
                    t.next = node; //虚拟头结点t--->B
                    return t;
                }
            }
        }
    }
    
    //CAS head field. Used only by enq.
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    //CAS tail field. Used only by enq.
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    
    ...
}

0922
线程B加入等待队列。

23_AQS源码深度解读05

线程A依然工作,线程C如线程B那样炮制加入等待队列。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	...

     * Acquires in exclusive mode, ignoring interrupts.  Implemented
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//线程C调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//线程C加入等待队列
            selfInterrupt();//下一节论述
    }
    
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode); //此时是线程C 模式是排他 此时等候队列有虚拟头结点 结点B
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;//此时前驱结点pred=尾结点tail=B
        //将新节点快速添加至队尾
        if (pred != null) {//B!=null
            node.prev = pred; //B<---C
            if (compareAndSetTail(pred, node)) {//尾指针tail--->C
                pred.next = node;//前驱结点B--->C
                return node;//返回
            }
        }
        enq(node);//快速添加至队尾失败,则用这方法调用(可能链表为空,才调用该方法)
        return node;
    }
    
    ...
}

0015

  • acquire() ----源码和三大流程
    0610

  • tryAcquire(arg)-----本次走非公平锁
    0708
    -----nonfairTryAcquire(acquires)

  • true-----继续推进条件,走下一个方法addWaiter

  • false-----结束
    0735

  • addWaiter(Node.EXCLUSIVE)

    • addwaiter (Node mode)
      • enq(node);
      • 双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。
    • 假如3号Threadc线程进来
      • prev
      • compareAndSetTail
      • next

24_AQS源码深度解读06

  • acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

    • acquireQueued
      0034
      • 假如再抢抢失败就会进入
  • shouldParkAfterFailedAcquire和 parkAndChecklnterrupt方法中
    0116

  • shouldParkAfterFailedAcquire

    • 如果前驱节点的 waitStatus是SIGNAL状态,即 shouldParkAfterFailedAcquire方法会返回true程序会继续向下执行parkAndCheckInterrupt方法,用于将当前线程挂起
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 		//获取前驱结点的状态
        int ws = pred.waitStatus;
       //如果是SIGNAL状态,即等待被占用的资源释放,直接返回 true
       //准备继续调用 parkAndCheckInterrupt方法
        if (ws == Node.SIGNAL)
            return true;
        //ws大于0说明是CANCELLED状态,
        if (ws > 0) {//跳过
           //循环判断前驱节点的前驱节点是否也为CANCELLED状态,忽略该状态的节点,重新连接队列
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        //将当前节点的前驱节点设置为设置为SIGNAL状态,用于后续唤醒操作
		//程序第一次执行到这返回为false,还会进行外层第二次循环,最终从代码第7行返回
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • parkAndCheckInterrupt
    0224

程序测试

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	...

     * Acquires in exclusive mode, ignoring interrupts.  Implemented
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//线程B调用非公平锁的tryAcquire(), 最终返回false,加上!,也就是true,也就是还要执行下面两行语句
            //线程B加入等待队列,acquireQueued本节论述<--------------------------
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();//
    }
    
    //Acquires in exclusive uninterruptible mode for thread already inqueue. 
    //Used by condition wait methods as well as acquire.
    //
    //return true if interrupted while waiting
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;//是否被取消
        try {
            boolean interrupted = false//是否被打断
            for (;;) {
                final Node p = node.predecessor();//1.返回前驱节点,对与线程B来说,p也就是傀儡节点 对线程C的前驱结点是B
				//p==head为true,tryAcquire()方法说明请转至 #21_AQS源码深度解读03
                //假设线程A正在工作,现在线程B只能等待,所以tryAcquire(arg)返回false,下面的if语块不执行
                //
                //第二次循环,假设线程A继续正在工作,下面的if语块还是不执行 对于C线程 进入不了
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //请移步到2.处的shouldParkAfterFailedAcquire()解说。第一次返回false, 下一次(第二次)循环
                //第二次循环,shouldParkAfterFailedAcquire()返回true,执行parkAndCheckInterrupt()
                if (shouldParkAfterFailedAcquire(p, node) && 
                    //4. 
                    parkAndCheckInterrupt())//park导致BC阻塞到这方法里了
                    interrupted = true;//原来一直想tryAcquire,现在正在阻塞了,在候客区了
            }
        } finally {
            if (failed)
                cancelAcquire(node);//取消排队
        }
    }
    
    
    static final class Node {

        ...
        //1.返回前一节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        
        ...

    }
    
    //2. 
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//此时pred指向傀儡节点,它的waitStatus为0
        //Node.SIGNAL为-1,跳过
        //第二次调用,ws为-1,条件成立,返回true
        if (ws == Node.SIGNAL)//-1
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {//跳过
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            //3. 傀儡节点的WaitStatus设置为-1//下图红圈
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;//第一次返回
    }
    
    /**
     * CAS waitStatus field of a node.
     */
    //3.
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }
    
    /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    //4.
    private final boolean parkAndCheckInterrupt() {
        //前段章节讲述的LockSupport,this指的是NonfairSync对象,
        //这意味着真正阻塞线程B,同样地阻塞了线程C
        LockSupport.park(this);//线程B,C在此处暂停了运行<-------------------------
        return Thread.interrupted();
    }
    
}

0609
图中的B结点把傀儡节点的waitStatus由0变为-1(Node.SIGNAL)。

C结点会把B结点的waitStatus由0变为-1(Node.SIGNAL)。

25_AQS源码深度解读07

接下来讨论ReentrantLock.unLock()方法。假设线程A工作结束,调用unLock(),释放锁占用。

方法unlock()

  • sync.release(1);
    • tryRelease(int arg);
      • unparkSuccessor
        • 杀回马枪
public class ReentrantLock implements Lock, java.io.Serializable {
    
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        
        ...
        //2.unlock()间接调用本方法,releases传入1
        protected final boolean tryRelease(int releases) {
            //3.
            int c = getState() - releases;//c为0
            //4.
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//c为0,条件为ture,执行if语句块
                free = true;
                //5.
                setExclusiveOwnerThread(null);
            }
            //6.
            setState(c);//设置状态为0
            return free;//最后返回true
        }
    	...
    
    }
    
    static final class NonfairSync extends Sync {...}
    
    public ReentrantLock() {
        sync = new NonfairSync();//我们使用的非公平锁
    }
    					//注意!注意!注意!
    public void unlock() {//<----------从这开始,假设线程A工作结束,调用unLock(),释放锁占用
        //1.
        sync.release(1);//在AbstractQueuedSynchronizer类定义
    }
    
    ...
 
}

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    ...
    //1.
    public final boolean release(int arg) {
        //2.
        if (tryRelease(arg)) {//该方法看子类NonfairSync实现,最后返回true
            Node h = head;//返回傀儡节点
            if (h != null && h.waitStatus != 0)//傀儡节点非空,且状态为-1,条件为true,执行if语句
                //7.
                unparkSuccessor(h);
            return true;
        }
        return false;//返回true,false都无所谓了,unlock方法只是简单调用release方法,对返回结果没要求
    }
    
    /**
     * The synchronization state.
     */
    private volatile int state;

    //3.
    protected final int getState() {
        return state;
    }

    //6.
    protected final void setState(int newState) {
        state = newState;
    }
    
    //7. Wakes up node's successor, if one exists.
    //传入傀儡节点
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;//傀儡节点waitStatus为-1
        if (ws < 0)//ws为-1,条件成立,执行if语块
            compareAndSetWaitStatus(node, ws, 0);//8.将傀儡节点waitStatus由-1变为0

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;//傀儡节点的下一节点,也就是带有线程B的节点
        if (s == null || s.waitStatus > 0) {//s非空,s.waitStatus非0,条件为false,不执行if语块
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)//s非空,条件为true,执行if语块
            LockSupport.unpark(s.thread);//唤醒线程B。运行到这里,线程A的工作基本告一段落了。
    }
    
    //8.
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }
    
    
}

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    ...

    protected AbstractOwnableSynchronizer() { }

    private transient Thread exclusiveOwnerThread;

    //5.
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    
    //4.
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

线程A结束工作,调用unlock()的tryRelease()后的状态,state由1变为0,exclusiveOwnerThread由线程A变为null。
0915

线程B被唤醒,即从原先park()的方法继续运行

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

     private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//线程B从阻塞到非阻塞,继续执行
        return Thread.interrupted();//线程B没有被中断,返回false
    }
    
	...
 
    //Acquires in exclusive uninterruptible mode for thread already inqueue. 
    //Used by condition wait methods as well as acquire.
    //
    //return true if interrupted while waiting
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();//线程B所在的节点的前一节点是傀儡节点
                //傀儡节点是头节点,tryAcquire()的说明请移步至#21_AQS源码深度解读03
                //tryAcquire()返回true,线程B成功上位
                if (p == head && tryAcquire(arg)) {
                    setHead(node);//1.将附带线程B的节点的变成新的傀儡节点
                    p.next = null; // help GC//置空原傀儡指针与新的傀儡节点之间的前后驱指针,方便GC回收
                    failed = false;
                    return interrupted;//返回false,跳到2.acquire()
                }
               
                if (shouldParkAfterFailedAcquire(p, node) && 
                    //唤醒线程B继续工作,parkAndCheckInterrupt()返回false
                    //if语块不执行,跳到下一循环
                    parkAndCheckInterrupt())//<---------------------------------唤醒线程在这里继续运行
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
    //1. 
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }
    
    //2.
    * Acquires in exclusive mode, ignoring interrupts.  Implemented
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            //acquireQueued()返回fasle,条件为false,if语块不执行,acquire()返回
            //也就是说,线程B成功获得锁,可以展开线程B自己的工作了。
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();//
    }
    
}

最后,线程B上位成功。

在这里插入图片描述

26_AQS小总结

aqs脑图

请添加图片描述

最后

2023-2-2 17:12:40

这篇博客能写好的原因是:站在巨人的肩膀上

这篇博客要写好的目的是:做别人的肩膀

开源:为爱发电

学习:为我而行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(C语言)程序环境和预处理

问&#xff1a;1. 什么是C语言的源代码&#xff1f;2. 由于计算机只认识什么&#xff1f;因此它只能接收与执行什么&#xff1f;也就是什么&#xff1f;3. 在ANSI C的任何一种实现中&#xff0c;存在哪两个不同的环境&#xff1f;在这两种环境里面分别干什么事情&#xff1f;4.…

一款非常不错的微信系统垃圾清理工具:微信清理大师,操作简单,清除较快。

微信清理大师 微信清理大师是一款专为微信所推出的系统垃圾清理工具。它的功能十分强大&#xff0c;可快捷清理微信内储存垃圾文件&#xff0c;操作十分简单&#xff0c;只需要轻轻一点&#xff0c;即可删除清理。 功能特点&#xff1a; 【一键清理】一键搞定无用垃圾&#x…

【计算机网络】Linux环境中的TCP网络编程

文章目录前言一、TCP Socket API1. socket2. bind3. listen4. accept5. connect二、封装TCPSocket三、服务端的实现1. 封装TCP通用服务器2. 封装任务对象3. 实现转换功能的服务器四、客户端的实现1. 封装TCP通用客户端2. 实现转换功能的客户端五、结果演示六、多进程版服务器七…

Kubernetes + Docker 部署一个yolov5检测服务(基于FastDeploy)

Kubernetes Docker 从零部署一个yolov5检测服务&#xff0c;服务基于PaddlePaddle/FastDeploy的服务化部署&#xff1b;所有软件从零安装。 文章目录1.说明2.环境3.安装过程 3.1安装 Docker 3.2安装 minikube 3.3安装 Kubectl4.部署过程 4.1 Docker相关 4.2 k8s相关 4.3 启动服…

开发必备技术--docker(使用篇)

文章目录前言Docker的基本概念概念数据卷虚拟网络镜像操作镜像名称镜像命令容器操作基本操作容器创建数据卷操作创建和查看数据卷其他指令实战前言 续接上一篇博文&#xff1a; 开发必备技术–docker&#xff08;一&#xff09; 这也是开学了&#xff0c;假期的最后一篇博文&a…

minio下载文件速度很慢的原因分析与说明

文章目录1.实战背景2.问题描述3.问题分析4.问题解决1.实战背景 最近在做一个项目&#xff0c;需要用到minio来搭建文件系统&#xff0c;先简单说一下我在项目中设置的上传文件流程&#xff1a; 前端将分块文件逐一传给后端&#xff0c;后端再存储到 linux服务器的minio 当中。…

JAVA集合专题3 —— vector + LinkedList + Set

目录vector的特点LinkedList底层结构模拟双向链表比较ArrayList和LinkedListSet接口基本介绍Set接口的遍历方式Set接口实现类对象的特点Set接口实现类HashSet模拟HashSet/HashMap的底层结构vector的特点 Vector底层是一个对象数组Vector是线程同步的&#xff0c;即线程安全的&…

保姆级 | ChatGPT接入微信教程

文章目录 0x00 前言 0x01 环境说明 0x02 准备工作 0x03 报错 Not available 解决方法 0x04 登录Open AI账号 0x05 获取账号API 0x06 配置阿里云开源项目 0x07 OpenAI接入微信 0x08 ChatGPT微信使用演示 0x09 参考文献 0x10 总结 0x00 前言 ChatGPT 美国 OpenAI 研发…

使用 Sahi 实现 Web 自动化测试

Sahi 是 Tyto Software 旗下的一个基于业务的开源 Web 应用自动化测试工具。Sahi 运行为一个代理服务器&#xff0c;并通过注入 JavaScript 来访问 Web 页面中的元素。Sahi 支持 HTTPS 并且独立于 Web 站点&#xff0c;简单小巧却功能强大。它相对于 Selenium 等自动化测试工具…

【408】操作系统 - 刻骨铭心自测题1(上)

文章目录OS练习题第一部分&#xff1a;1&#xff1a;2&#xff1a;3&#xff1a;4&#xff1a;5&#xff1a;6&#xff1a;7&#xff1a;8&#xff1a;9&#xff1a;10&#xff1a;11&#xff1a;12&#xff1a;13&#xff1a;14&#xff1a;15&#xff1a;16&#xff1a;17&am…

C++ 类与对象(下)

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;C类与对象的收尾工作&#…

Android10/11 原生Launcher3深度定制

一、引言关于Android10和11系统Launcher3的定制有很多&#xff0c;根据项目的需求会进行各种定制开发&#xff0c;于是就需要研究Launcher3的源码。本文主要从Android 11的Launcher3QuickStep着手&#xff08;go版本或者其他版本类似&#xff09;从常用的修改进行分析&#xff…

[论文阅读] DAE-GCN: Identifying Disease-Related Features for Disease Prediction

[论文地址] [代码] [MICCAI 21] Abstract 学习与疾病相关的表征在基于图像的癌症诊断中起着至关重要的作用&#xff0c;因为它具有可信、可解释和良好的概括能力。一个好的表征不仅应该与疾病无关的特征相分离&#xff0c;而且还应该包含病变的属性信息&#xff08;如形状、边…

【博客616】prometheus staleness对PromQL查询的影响

prometheus staleness对PromQL查询的影响 1、prometheus staleness 官方文档的解释&#xff1a; 概括&#xff1a; 运行查询时&#xff0c;将独立于实际的当前时间序列数据选择采样数据的时间戳。这主要是为了支持聚合&#xff08;sum、avg 等&#xff09;等情况&#xff0c…

【ChatGpt】——不一样的使用感受分享

作者&#xff1a;狮子也疯狂 专栏&#xff1a;《基础知识查漏》 坚持做好每一步&#xff0c;幸运之神自然会降临在你的身上 目录一. &#x1f981; 前言二. &#x1f981; 使用详情Ⅰ. &#x1f407; 使用过程Ⅱ. &#x1f407; 使用感受Ⅲ. &#x1f407; 遇到的问题3.1 我遇…

linux服务器挂载硬盘/磁盘

1. 查看机器所挂硬盘个数及分区情况&#xff1a;fdisk -l可以看出来目前/dev/vda 目前有300G可用.内部有两个分区&#xff08;/dev/vda1,/dev/vda2&#xff09;。2. 格式化磁盘格式化磁盘命令为【mkfs.磁盘类型格式 目录路径组成】查看磁盘文件格式&#xff1a;df -T格式化磁盘…

SharkTeam:Move合约开发与合约安全

近期&#xff0c;围绕 Aptos 和 Sui&#xff0c;新兴的高性能 L1链 以及这些新链背后的 Move 智能合约编程语言引起了很多关注&#xff0c;社区也非常活跃&#xff0c;很多开发者和项目已经开始积极转向 Move。但Move相对Solidity差别较大&#xff0c;即使是相对比较接近的Rust…

数据与C(字符串)

目录 一.概念引入 二.字符串&#xff08;数组存储&#xff0c;必须以\0结尾&#xff09; 三.错误示范 四.strlen&#xff08;&#xff09;和sizeof()相对于字符串的不同 一.概念引入 “a”,a哪个是字符哪个又是字符串&#xff0c;嘿嘿不用猜了 我们在上一章中说过&#x…

服务端开发Java面试复盘篇1

上周投了一些简历&#xff0c;约了8-9家面试&#xff0c;其中完成了3家的第一轮面试&#xff0c;由于面试的是Java 的实习生&#xff0c;感觉问的题目都比较基础&#xff0c;不过有些问题回答的不是很好&#xff0c;在这里对回答的不太好的题目做一下总结和复盘。 目录 一、后…

【数据库】 mysql用户授权详解

目录 MySQL用户授权 一&#xff0c;密码策略 1&#xff0c;查看临时密码 2&#xff0c;查看数据库当前密码策略&#xff1a; 二&#xff0c; 用户授权和撤销授权 1、创建用户 2&#xff0c;删除用户 3&#xff0c;授权和回收权限 MySQL用户授权 一&#xff0c;密码策略…