JUC之LockSupport,AQS

1.题目+说明

image-1667175405559

核心概念提取

  • synchronized与可重入锁
  • AQS

2.可重入锁(又名递归锁)

说明

image-1667175489365

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

  • 可:可以
  • 重:再次
  • 入:进入
  • 锁:同步锁
  • 进入什么:
    • 进入同步域(即同步代码块/方法或显式锁锁定的代码)
  • 一句话
    • 一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入
    • 自己可以获取自己的内部锁

可重入锁种类

  • 隐式锁(即synchronized关键字使用的锁)默认是可重入锁
    • 同步块
    • 同步方法
  • synchronized的重入的实现机理
    image-1667175558235
  • 显式锁(即lock)也有 ReentrantLock这样的可重入锁
/**
 * 可重入锁:可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
 * <p>
 * 在一个synchronized修饰的方法或代码块的内部
 * 调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的
 */
public class ReEnterLockDemo {
    static Object objectLockA = new Object();

    //同步代码块
    /*public static void m1() {
        new Thread(() -> {
            synchronized (objectLockA) {
                System.out.println(Thread.currentThread().getName() + "\t" + "------------------外层调用");
                synchronized (objectLockA) {
                    System.out.println(Thread.currentThread().getName() + "\t" + "------------------中层调用");
                    synchronized (objectLockA) {
                        System.out.println(Thread.currentThread().getName() + "\t" + "------------------内层调用");
                    }
                }
            }
        }, "t1").start();
    }*/

    //同步方法
    /*public synchronized void m1() {
        System.out.println("=======外");
        m2();
    }

    public synchronized void m2() {
        System.out.println("=======中");
        m3();
    }

    public synchronized void m3() {
        System.out.println("=======内");
    }*/

    static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        //m1();
        //new ReEnterLockDemo().m1();
        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

LockSupport是什么

image-1667175663677

  • 是什么
    image-1667175678661

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

  • 3种让线程等待和唤醒的方法
    image-1667175707989
  • Object类中wait和notify方法实现线程等待和唤醒
    • 代码
      • 正常
      • 异常1
        image-1667175832794
        • wait方法和notify方法,两个都去掉同步代码块
        • 异常情况
          image-1667175880789
      • 异常2
        image-1667175902916
        • 将notify放在wait方法前面
        • 程序无法执行,无法唤醒
    • 小总结
      image-1667175969468
    • 完整代码
    public class LockSupportDemo {
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();
    
    public static void main(String[] args) {//main方法,主线程一切程序入口
        //1.场景1:先unpark,再park【park相当于形同虚设,可以注释掉】
        //2.场景2:park和unpark不成对
        Thread a = new Thread(() -> {
    
            System.out.println(Thread.currentThread().getName() + "\t" + "-------------come in" + System.currentTimeMillis());
            LockSupport.park();//被阻塞....等待通知等待放行,它要通过需要许可证
            // LockSupport.park();//被阻塞....等待通知等待放行,它要通过需要许可证
            System.out.println(Thread.currentThread().getName() + "\t" + "----------------被唤醒" + System.currentTimeMillis());
    
        }, "a");
        a.start();
        //暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(3L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
    
        Thread b = new Thread(() -> {
            LockSupport.unpark(a);//线程等待,让unpark先运行
            // LockSupport.unpark(a);//线程等待,让unpark先运行
            System.out.println(Thread.currentThread().getName() + "\t" + "----------------通知了");
    
        }, "b");
        b.start();
    
    }
    
    /**
     * 场景1:注释掉lock和unlock
     * 场景2:先signal再await
     */
    private static void lockAwaitSignal() {
        new Thread(() -> {
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(3L);
            } 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();
    }
    
    /**
     * 场景1:注释掉synchronized试试看效果
     * 场景2:先notify(睡几秒),然后再wait
     */
    private static void synchronizedWaitNotify() {
        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();
    }
    

}
```

  • Condition接口中的await后 signal方法实现线程的等待和唤醒
    • 同Object类wait和notify
      image-1667175991533
  • 传统的synchronized和Lock实现等待唤醒通知的约束
    image-1667176013473
  • LockSupport类中的park等待和unpark唤醒
    • 是什么
      • 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
      • 官网解释
        image-1667176048843
    • 主要方法
      • API
        image-1667176078478
      • 阻塞
        • park/park(Object blocker)
          image-1667176108909
        • 阻塞当前线程/阻塞传入的具体线程
      • 唤醒
        • unpark(Thread thread)
          image-1667176141839
        • 唤醒处于阻塞状态的指定线程
    • 代码
      • 正常+无锁块要求
      • 之前错误的先唤醒后等待,LockSupport照样支持
        • 解释
          image-1667176201461
    • 重点说明(重要)
      image-1667176236216
    • 面试题
      image-1667176252782

4.AbstractQueuedSynchronizer之AQS

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

  • 面试题复盘
    image-1667176324587
  • 前置知识
    • 公平锁和非公平锁
    • 可重入锁
    • LockSupport
    • 自旋锁
    • 数据结构之链表
    • 设计模式之模板设计模式
  • 是什么
    • 字面意思
      • 抽象的队列同步器
      • 源代码
        image-1667176402100
    • 技术解释
      image-1667176459158
  • AQS为什么是JUC内容中最重要的基石
    • 和AQS有关的
      image-1667176492735
      • ReentrantLock
        image-1667176507653
      • CountDownLatch
        image-1667176523202
      • ReentrantReadWriteLock
        image-1667176537823
      • Semaphore
        image-1667176551856
    • 进一步理解锁和同步器的关系
      • 锁, 面向锁的使用者
        • 定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
      • 同步器,面向锁的实现者
        • 比如Java并发大神DougLee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等
  • 能干嘛
    • 加锁会导致阻塞
      • 有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
    • 解释说明
      image-1667176736334

AQS初步

  • AQS初识
    • 官网解释
      image-1667177966433
    • 有阻塞就需要排队,实现排队必然需要队列
      image-1667177984389
  • AQS内部体系架构
    image-1667178007238
    • AQS自身
      • AQS的int变量
        • AQS的同步状态State成员变量
          image-1667178075866
        • 银行办理业务的受理窗口状态
          • 零就是没人,自由状态可以办理
          • 大于等于1,有人占用窗口,等着去
      • AQS的CLH队列
        • CLH队列(三个大牛的名字组成),为一个双向队列
          image-1667178115670
        • 银行候客区的等待顾客
      • 小总结
        • 有阻塞就需要排队,实现排队必然需要队列
        • state变量+CLH变种的双端队列
    • 内部类Node(Node类在AQS类的内部)
      • Node的int变量
        • Node的等待状态waitState成员变量
          • volatile int waitStatus
        • 说人话
          • 等候区其它顾客(其它线程)的等待状态
          • 队列中每个排队的个体就是一个Node
      • Node此类的讲解
        • 内部结构
          image-1667178252425
        • 属性说明
          image-1667178266811
  • AQS同步队列的基本结构
    image-1667178339342

从我们的ReentrantLock开始解读AQS

  • Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成流程访问控制的
  • ReentrantLock的原理
    image-1667178386645
  • 从最简单的lock方法开始看看公平和非公平
    image-1667178412058
    image-1667178425914
  • 非公平锁走起,方法lock()
    image-1667178468464
    • 本次讲解我们走最常用的,lock/unlock作为案例突破口
    • 源码解读比较困难,别着急—阳哥的全系列脑图给大家做好笔记
    • AQS源码深度分析走起
      • lock()
        image-1667178518425
      • acquire()
        • 源码和3大流程走向
          image-1667178542408
      • tryAcquire(arg)
        • 本次走非公平锁
          image-1667178579104
          • nonfairTryAcquire(acquires)
            image-1667178594787
            • return false;
            继续推进条件,走下一个方法addWaiter
            
            • return true;
            结束
            
      • addWaiter(Node.EXCLUSIVE)
        • addWaiter(Node mode)
          image-1667178667531
          • enq(node);
            image-1667178680888
          • 双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位
            真正的第一个有数据的节点,是从第二个节点开始的
        • 假如3号ThreadC线程进来
          • prev
          • compareAndSetTail
          • next
      • acquireQueued(addWaiter(Node.EXCLUSIVE),arg)
        • acquireQueued
          image-1667178790138
          • 假如再抢抢失败就会进入
            • shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法中
              image-1667178808899
            • shouldParkAfterFailedAcquire
              image-1667178832988
            • parkAndCheckInterrupt
              image-1667178862464
      • 方法unlock()
        • sync.release(1);
          • tryRelease(arg)
            • unparkSuccessor
              • 杀回马枪
                image-1667178921246

彩蛋

1.synchronized-自动挡,lock-手动挡

2.从字节码角度分析synchronized实现

image-1667178971843
image-1667178983187

3.学习新技术,要对比学它前和学它后

image-1667178999275

4.jdk官方在线手册

jdk8 api

5.学习三部曲(三把斧):理论+实操+小总结

避免成为api调用工程师

6.AQS与HashMap的形象对比

image-1667179036319

7.ReentrantLock加锁过程图解

image-1667179050712

8.三个顾客来顾客办理业务代码图例解释

  • image-1667179066290
  • image-1667179077574
  • image-1667179086688
  • image-1667179095227
  • image-1667179102555
最近的文章

Spring相关

4.SpringSpring的aop顺序AOP常用注解@Before前置通知:目标方法之前执行@After后置通知:目标方法之后执行(始终执行)@AfterReturning返回后通知:执行方法结束前执行(异常不执行)@AfterThrowing异常通知:出现异常时候执行@Around环绕通知:环绕…

继续阅读
更早的文章

字节跳动两数求和

面试题字节快手面试复盘力扣第一题:两数之和题目说明https://leetcode.cn/problems/two-sum/解法暴力法:通过双重循环遍历数组中所有元素的两两组合,当出现符合的和时,返回两个元素的下标哈希(更优解法)考查点你都想来大厂了,算法居然从来没有刷过??呵呵机会偏爱有准备有实力…

继续阅读