赞
踩
通过代码解释为什么JUC是最重要的基石
(1). 和AQS有关的
(2).ReentrantLock
(3).CountDownLatch
(4).ReentrantReadWriteLock
(5). Semaphore
③. 锁,面向锁的使用者(定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可)
同步器,面向锁的实现者(比如Java并发大神Douglee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。)
④. 加锁会导致阻塞、有阻塞就需要排队,实现排队必然需要队列
⑤. 如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node) ,通过CAS、自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果
写在最前面:
(1). 本次讲解我们走最常用的,lock/unlock作为案例突破口
(2). 我相信你应该看过源码了,那么AQS里面有个变量叫State,它的值有几种?3个状态:没占用是0,占用了是1,大于1是可重入锁
(3). 如果AB两个线程进来了以后,请问这个总共有多少个Node节点?答案是3个,其中队列的第一个是傀儡节点(哨兵节点)
业务图:
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("-----A thread come in"); try { TimeUnit.MINUTES.sleep(20); }catch (Exception e) {e.printStackTrace();} }finally { lock.unlock(); } },"A").start(); //第二个顾客,第二个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待, //进入候客区 new Thread(() -> { lock.lock(); try{ System.out.println("-----B thread come in"); }finally { lock.unlock(); } },"B").start(); //第三个顾客,第三个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待, //进入候客区 new Thread(() -> { lock.lock(); try{ System.out.println("-----C thread come in"); }finally { lock.unlock(); } },"C").start(); } }
假如3号ThreadC线程进来
(1). prev
(2). compareAndSetTail
(3). next
①. acquireQueued
(会调用如下方法:shouldParkAterFailedAcquire和parkAndCheckInterrupt | setHead(node) )
②. shouldParkAfterFailedAcquire
③. parkAndCheckInterrupt
④. 当我们执行下图中的③表示线程B或者C已经获取了permit了
⑤. setHead( )方法
代码执行完毕后,会出现如下图所示
①. 业务场景,比如说我们有三个线程A、B、C去银行办理业务了,A线程最先抢到执行权开始办理业务,那么B、C两个线程就在CLH队列里面排队如图所示,注意傀儡结点和B结点的状态都会改为-1
②. 当A线程办理好业务,离开的时候,会把傀儡结点的waitStatus从-1改为0 | 将status从1改为0,将当前线程置为null
③. 这个时候如果B上位,首先将status从0改为1(表示占用),把thread置为线程B | 会执行如下图的①②③④,会触发GC,然后就把第一个灰色的傀儡结点给清除掉了,这个时候原来的B结点重新成为傀儡结点
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。