Java⾯试必备之AQS阻塞队列和条件队列
⼀.AQS⼊队规则
我们仔细分析⼀下AQS是如何维护阻塞队列的,在独占⽅式获取资源的时候,是怎么将竞争锁失败的线程丢到阻塞队列中的呢?
我们看看acquire⽅法,这⾥⾸先会调⽤⼦类实现的tryAcquire⽅法尝试修改state,修改失败的话,说明线程竞争锁失败,于是会⾛到后⾯的这个条件;
这个addWaiter⽅法就是将当前线程封装成⼀个Node.EXCLUSIVE类型的节点,然后丢到阻塞队列中;
第⼀次还没有阻塞队列的时候,会到enq⽅法⾥⾯,我们仔细看看enq⽅法
enq()⽅法中,我们在第⼀次进⼊这个⽅法的时候,下⾯图⼀所⽰,tail和head都指向null;
第⼀次循环,到⾸先会到图⼆,然后判断t所指向的节点是不是null,如果是的话,就⽤CAS更新节点,这个CAS我们可以看作:头节点head为null,我们把head节点更新为⼀个哨兵节点(哨兵节点就是new Node()),再将tail也指向head,就是图三了奇迹 英语
第⼆次for循环:⾛到上⾯的el语句,将新节点的前⼀个节点设置为哨兵节点;
然后就是CAS更新节点,这⾥CAS的意思:如果最后的节点tail指向的和t是⼀样的,那么就将tail指向node节点
trunk是什么意思
最后再将t的下⼀个节点设置为node,下图所⽰,就ok了
⼆.AQS条件变量的使⽤
什么是条件变量呢?我们在开始介绍AQS的时候,还有⼀个内部类没有说,就是ConditionObject,还记得前⾯说过的Unsafe中的park和unpark⽅法吗?⽽这个ConditionObject就对这两个⽅法进⾏了⼀次封装,await()和signal()⽅法,但是更灵活,可以创建多个条件变量,每个条件变量维护⼀个条件队列(就是⼀个单向链表,可以看到Node这个内部类中个属性是nextWaiter);
注意:每⼀个条件变量⾥⾯都维护了⼀个条件队列
举个例⼦,如下所⽰;
ample.demo.study;
import urrent.locks.Condition;
import urrent.locks.ReentrantLock;
public class Study0201 {
public static void main(String[] args) throws InterruptedException {
/
/ 创建锁对象
ReentrantLock lock = new ReentrantLock();
// 创建条件变量
Condition condition = wCondition();
// 以下创建两个线程,⾥⾯都会获取锁和释放锁
Thread thread1 = new Thread(() -> {
lock.lock();
try {
System.out.println("await begin");
// 注意,这⾥调⽤条件变量的await⽅法,当前线程就会丢到condition条件变量中的条件队列中阻塞
condition.await();
System.out.println("await end");级差地租
} catch (InterruptedException e) {
//
} finally {
lock.unlock();
}
});
Thread thread2 = new Thread(() -> {tensioner
lock.lock();
try {
System.out.println("signal begin");
/
/ 唤醒被condition变量内部队列中的某个线程
condition.signal();
System.out.println("signal end");
} finally {
lock.unlock();
}
});
thread1.start();
Thread.sleep(500);
thread2.start();
}
}
还可以创建多个条件变量,如下所⽰,每⼀个条件变量都维护了⼀个条件队列:
ample.demo.study;
import urrent.locks.Condition;
import urrent.locks.ReentrantLock;
public class Study0201 {
public static void main(String[] args) throws InterruptedException {
// 创建锁对象
ReentrantLock lock = new ReentrantLock();
// 创建条件变量1
Condition condition1 = wCondition();
//条件变量2
Condition condition2 = wCondition();
// 以下创建两个线程,⾥⾯都会获取锁和释放锁
Thread thread1 = new Thread(() -> {
lock.lock();
try {
System.out.println("await begin");//1
condition1.await();
System.out.println("await end");//5
System.out.println("condition2---signal---start");//6
condition2.signal();
System.out.println("condition2---signal---endend");//7
} catch (InterruptedException e) {
//
} finally {
lock.unlock();
}
});
Thread thread2 = new Thread(() -> {
lock.lock();
try {
numeric
System.out.println("signal begin");//2
condition1.signal();
System.out.println("signal end");//3
System.out.println("condition2---await---start");//4
condition2.await();
System.out.println("condition2---await---end");//8
} catch (InterruptedException e) {
//
} finally {
lock.unlock();
}
});
thread1.start();
Thread.sleep(500);
thread2.start();
}
}
三.⾛进条件变量
我们看看上⾯的获取条件变量的⽅式Condition condition1 = wCondition(),我们打开newCondition⽅法,最后就是创建⼀个ConditionObject实例;这个类是AQS的内部类,通过这个类可以访问AQS内部的属性和⽅法;
注意:在调⽤await⽅法和signal⽅法之前,必须要先获取锁
然后我们再看看条件变量的await⽅法,下图所⽰,我们可以进⼊到addConditionWaiter()⽅法内部看看:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//新建⼀个Node.CONDITION节点放到条件队列最后⾯
Node node = addConditionWaiter();
//释放当前线程获取的锁
int savedState = fullyRelea(node);
int interruptMode = 0;
//调⽤park()⽅法阻塞挂起当前线程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (Waiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
普特英语听力
private Node addConditionWaiter() {
Node t = lastWaiter;
/
/第⼀次进来,这个lastWaiter是null,即t = null,不会进⼊到这个if语句
the ring
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建⼀个Node.CONDITION类型的节点,然后下⾯这个if中就是将第⼀个节点firstWaiter和最后⼀个节点都指向这个新创建的节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
el
lastWaiter = node;
return node;
}
顺便在看看signal⽅法:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//条件队列移除第⼀个节点,然后把这个节点丢到阻塞队列中,然后激活这个线程
Node first = firstWaiter;
if (first != null)believe是什么意思
doSignal(first);
}
我们想⼀想在AQS中阻塞队列和条件队列有什么关系啊?
1.当多个线程调⽤lock.lock()⽅法的时候,只有⼀个线程获取到可锁,其他的线程都会被转为Node节点丢到AQS的阻塞队列中,并做CAS⾃旋获取锁;
2.当获取到锁的线程对应的条件变量的await()⽅法被调⽤的时候,该线程就会释放锁,并把当前线程转为Node节点放到条件变量对应的条件队列中;
3.这个时候AQS的阻塞队列中⼜会有⼀个节点中的线程能得到锁了,如果这个线程⼜恰巧调⽤了对应条件变量的await()⽅法时,⼜会重复2的步骤,然后阻塞队列中⼜会有⼀个节点中的线程获得锁
4.然后,⼜有⼀个线程调⽤了条件变量的signal()或者signalAll()⽅法,就会把条件队列中⼀个或者所有的节点都移动到AQS阻塞队列中,然后调⽤unpark⽅法进⾏授权,就等着获得锁了;⼀个锁对应⼀个阻塞队列,但是对应多个条件变量,每⼀个条件变量对应⼀个条件队列;其中,这两种队列中存放的都是Node节点,Node节点中封装了线程及其状态
哒哒英语青少儿英语到此这篇关于Java⾯试必备之AQS阻塞队列和条件队列的⽂章就介绍到这了,更多相关AQS阻塞队列和条件队列内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!
draken怎么读