首页 > 编程语言 >3.JUC【Java面试第三季】

3.JUC【Java面试第三季】

时间:2023-05-21 19:07:51浏览次数:46  
标签:Node JUC Java Thread 第三季 lock 线程 java public


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.题目+说明

3.JUC【Java面试第三季】_面试

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

3.JUC【Java面试第三季】_面试_02

  • 是什么

3.JUC【Java面试第三季】_java_03

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)方法来实现阻塞和唤醒线程的操作
  • 官网解释
主要方法
  • API
  • 3.JUC【Java面试第三季】_System_04

  • 阻塞

    • park() /park(Object blocker)
    • 3.JUC【Java面试第三季】_System_05

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

  • 唤醒

    • unpark(Thread thread)
    • 3.JUC【Java面试第三季】_重入锁_06

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

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
  • 自旋锁
  • 数据结构之链表
  • 设计模式之模板设计模式

是什么

  • 字面意思

    • 抽象的队列同步器
    • 源代码
    • 3.JUC【Java面试第三季】_重入锁_07


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

3.JUC【Java面试第三季】_System_08


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

16_AQS能干嘛

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

和AQS有关的

3.JUC【Java面试第三季】_面试_09

  • ReentrantLock
  • 3.JUC【Java面试第三季】_重入锁_10

  • CountDownLatch
  • 3.JUC【Java面试第三季】_重入锁_11

  • ReentrantReadWriteLock
  • 3.JUC【Java面试第三季】_System_12

  • Semaphore
  • 3.JUC【Java面试第三季】_System_13

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

  • 锁,面向锁的 使用者

  • 同步器,面向锁的 实现者

能干嘛

  • 加锁会导致阻塞

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

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

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

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

3.JUC【Java面试第三季】_面试_14

17_AQS源码体系-上

AQS初步

AQS初识

  • 官网解释
  • 3.JUC【Java面试第三季】_java_15

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

    • 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) {
         
    ...
}

3.JUC【Java面试第三季】_面试_16

18_AQS源码体系-下

AQS内部体系架构

AQS自身
  • AQS的int变量

    • AQS的同步状态state成员变量
    • 3.JUC【Java面试第三季】_职场和发展_17

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

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

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

  • AQS的CLH队列

    • CLH队列(三个大牛的名字组成),为一个双向队列
    • 3.JUC【Java面试第三季】_java_18

    • 银行候客区的等待顾客

  • 小总结

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

    • Node的等待状态waitState成员变量
    • 3.JUC【Java面试第三季】_面试_19


    • 说人话
      • 等候区其它顾客(其它线程)的等待状态队列中每个排队的个体就是一个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
    }
	...
}
  • 属性说明
  • 3.JUC【Java面试第三季】_重入锁_20


AQS同步队列的基本结构

3.JUC【Java面试第三季】_面试_21

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的原理

3.JUC【Java面试第三季】_面试_22

从最简单的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();
    }

3.JUC【Java面试第三季】_System_23


3.JUC【Java面试第三季】_java_24

3.JUC【Java面试第三季】_System_25

可以明显看出公平锁与非公平锁的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(),之后还是需要竞争锁(存在线程竞争的情况下)

3.JUC【Java面试第三季】_面试_26

  • 本次讲解我们走最常用的,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();
    }
}

程序初始状态方便理解图

3.JUC【Java面试第三季】_System_27

  • lock()
  • 3.JUC【Java面试第三季】_System_28

  • 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开始办业务了。

3.JUC【Java面试第三季】_职场和发展_29

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);
    }

    
    ...
}

3.JUC【Java面试第三季】_重入锁_30


线程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;
    }
    
    ...
}

3.JUC【Java面试第三季】_重入锁_31

  • acquire() ----源码和三大流程
  • 3.JUC【Java面试第三季】_面试_32

  • tryAcquire(arg)-----本次走非公平锁
  • 3.JUC【Java面试第三季】_职场和发展_33

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

  • false-----结束
  • 3.JUC【Java面试第三季】_java_34

  • addWaiter(Node.EXCLUSIVE)

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

    • acquireQueued
    • 3.JUC【Java面试第三季】_职场和发展_35

      • 假如再抢抢失败就会进入
  • shouldParkAfterFailedAcquire和 parkAndChecklnterrupt方法中
  • 3.JUC【Java面试第三季】_面试_36

  • 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
  • 3.JUC【Java面试第三季】_面试_37


程序测试

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();
    }
    
}

3.JUC【Java面试第三季】_职场和发展_38


图中的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。

3.JUC【Java面试第三季】_职场和发展_39

线程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上位成功。

3.JUC【Java面试第三季】_面试_40

26_AQS小总结

aqs脑图

3.JUC【Java面试第三季】_职场和发展_41

最后

2023-2-2 17:12:40

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

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

开源:为爱发电

学习:为我而行




标签:Node,JUC,Java,Thread,第三季,lock,线程,java,public
From: https://blog.51cto.com/u_15719556/6319993

相关文章

  • 2.Java基础【Java面试第三季】
    2.Java基础【Java面试第三季】前言推荐2.Java基础01_字符串常量Java内部加载-上58同城的java字符串常量池面试code讲解intern()方法---源码+解释02_字符串常量Java内部加载-下whyOpenJDK8底层源码说明递推步骤总结考查点03_闲聊力扣算法第一题字节跳动两数求和题目说明面试题解法04......
  • java中使用jep调用python类
    经过调研,目前这应该只有一种调用方式了,那就是使用jep,后来亲测了以下确实是可行,我是使用jep调用了一个python文件中的类,并测试了类的一个方法,可以正常执行,但是具体速度会不会慢很多,我还没有测试。刚开始在调研的时候,说jython也可以调用,但是这个包只支持2.7python,毕竟现在很少有用2......
  • IDEA——Java的一些依赖
    <!--导入knife4j的maven坐标(Swagger框架)--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.2</ve......
  • Android 教你一步步搭建MVP+Retrofit+RxJava网络请求框架
    1.什么是MVP? MVP(ModelViewPresenter)其实就是一种项目的整体框架,能让你的代码变得更加简洁,说起框架大家可能还会想到MVC、MVVM。由于篇幅原因,这里我们先不讲MVVM,先来看一下MVC。其实Android本身就采用的是MVC(ModelViewControllor)模式、其中Model指的是数据逻辑和实体模型......
  • JavaSE的简单了解
    JavaSE简称为JavaStandardEdition,是Java编程语言的基础平台。JavaSE提供了一系列用于开发Java应用程序的API和工具,以及Java虚拟机(JVM)和编译器。JavaSE是Java技术生态系统的核心,是Java应用程序开发的基础。 编译执行过程数据类型分类 JavaSE框架包括以下部分:......
  • JavaSE的简单了解
    JavaSE简称为JavaStandardEdition,是Java编程语言的基础平台。JavaSE提供了一系列用于开发Java应用程序的API和工具,以及Java虚拟机(JVM)和编译器。JavaSE是Java技术生态系统的核心,是Java应用程序开发的基础。 编译执行过程数据类型分类 JavaSE框架包括以下部分:......
  • Java面向对象中“匿名对象”的使用
    1.0匿名对象的基本知识匿名对象顾名思义,匿名对象指的就是没有名字的对象,在使用中理解为实例化一个类对象,但是并不把它赋给一个对应的类变量,而是直接使用。在理解匿名对象前,我们先创建一个类便于后面的使用。匿名对象具有以下特征:语法上:只创建对象,但不用变量来接收,例如:假设现......
  • java list.stream 多条件去重(分组)
    List<EmEventConfigPointExcelDto>listNew=list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(()->newTreeSet<>(Comparator.comparing((o)-......
  • java基于springboot+vue的土特产在线销售平台、特产在线销售商城,附源码+数据库+lw文档
    1、项目介绍考虑到实际生活中在藏区特产销售管理方面的需要以及对该系统认真的分析,将系统权限按管理员和用户这两类涉及用户划分。(1)管理员功能需求管理员登陆后,主要模块包括首页、个人中心、用户管理、特产信息管理、特产分类管理、特产分类管理、特产评分管理、系统管理、订单......
  • JavaScript学习笔记:模块
    前言在js编程中,模块指的是按照一定格式将代码以功能拆分后作为独立文件存在的一个实体。早期的JS并没有规定模块应该如何设计,核心语言也没有针对模块提供相关支持。早期的代码使用IIFE来实现一个模块,它是通过向全局对象添加属性来实现与其他模块来交互的。(function(){v......