/** * 汽车加油 */
public void fuelUp(int num){
lock.lock();
try {
while (flag!=1){
c1.await();}
System.out.println("第"+num+"辆车开始加油");
flag =2;
c2.signal();} catch (InterruptedException e){
e.printStackTrace();} finally {
lock.unlock();}}/** * 汽车清洗 */
public void carWash(int num){
lock.lock();
try {
while (flag!=2){
c2.await();}
System.out.println("第"+num+"辆车开始清洗");
flag =3;
c3.signal();} catch (InterruptedException e){
e.printStackTrace();} finally {
lock.unlock();}}/** * 驶离 */
public void drive(int num){
lock.lock();
try {
while (flag!=3){
c3.await();}
System.out.println("第"+num+"辆车已经驶离加油站");
flag =1;
c1.signal();} catch (InterruptedException e){
e.printStackTrace();} finally {
lock.unlock();}}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
其中await为等待方法,signal为唤醒方法。
最后我们来定义main方法,模拟一下3辆车同时到达加油站的场景
public static void main(String[] args){
CarOperation carOperation = new CarOperation();//汽车加油
new Thread(()->{
for (int i =1; i <4; i++){
carOperation.fuelUp(i);}},"fuelUp").start();//汽车清洗
new Thread(()->{
for (int i =1; i <4; i++){
carOperation.carWash(i);}},"carRepair").start();//驶离
new Thread(()->{
for (int i =1; i <4; i++){
carOperation.drive(i);}},"drive").start();}
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();//将当前线程封装成node加入等待队列尾部
Node node = addConditionWaiter();int savedState = fullyRelease(node);int interruptMode =0;//检测此节点的线程是否在同步队上,如果不在,则说明该线程还不具备竞争锁的资格,则继续等待直到检测到此节点在同步队列上
while (!isOnSyncQueue(node)){//当node处于等待队列时,挂起当前线程。
LockSupport.park(this);//如果发生了中断,则跳出循环,结束等待
if ((interruptMode = checkInterruptWhileWaiting(node))!=0)
break;}//被唤醒后该节点一定会在AQS队列上,
//之前分析过acquireQueued方法获取不到锁会继续阻塞
//获取到了锁,中断过返回true,未中断过返回false
//获取到锁存在中断并且不是中断唤醒的线程将中断模式设置为重新中断
if (acquireQueued(node, savedState)&& interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter!=null)// clean up if cancelled
//清除条件队列中所有状态不为 CONDITION 的结点
unlinkCancelledWaiters();
if (interruptMode !=0)
reportInterruptAfterWait(interruptMode);}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
如果线程中断,清除中断标记并抛出异常。
查看addConditionWaiter
该方法的作用是将当前线程封装成node加入等待队列尾部
private Node addConditionWaiter(){
Node t = lastWaiter;
if (t !=null&& t.waitStatus!= Node.CONDITION){//将不处于等待状态的结点从等待队列中移除
unlinkCancelledWaiters();
t = lastWaiter;}
Node node = new Node(Thread.currentThread(), Node.CONDITION);//尾节点为空
if (t ==null)//将首节点指向node
firstWaiter = node;
else
//将尾节点的nextWaiter指向node节点
t.nextWaiter= node;//尾节点指向node
lastWaiter = node;
return node;}
final int fullyRelease(Node node){//释放锁失败为true,释放锁成功为false
boolean failed =true;
try {//获取当前锁的state
int savedState = getState();//释放锁成功的话
if (release(savedState)){
failed =false;
return savedState;} else {
throw new IllegalMonitorStateException();}} finally {
if (failed)//释放锁失败的话将节点状态置为取消
node.waitStatus= Node.CANCELLED;}}
private boolean findNodeFromTail(Node node){
Node t = tail;
for (;;){//先用尾节点来判断,然后用队列中的节点依次来判断
if (t == node)
return true;//节点为空,说明找到头也不在AQS队列中,返回false
if (t ==null)
return false;
t = t.prev;}}
private int checkInterruptWhileWaiting(Node node){
return Thread.interrupted()?(transferAfterCancelledWait(node)? THROW_IE : REINTERRUPT):0;}
1.
2.
3.
4.
5.
我们来看看transferAfterCancelledWait方法是如果区分1和-1的
final boolean transferAfterCancelledWait(Node node){//cas尝试将node的waitStatus设置为0
if (compareAndSetWaitStatus(node, Node.CONDITION,0)){//将node节点由等待队列加入AQS队列
enq(node);
return true;}//cas失败后,看看队列是不是已经在AQS队列中,如果不在,则通过yield方法给其它线程让路
while (!isOnSyncQueue(node))
Thread.yield();//如果已经在AQS队列中,则返回false
return false;}
public class CarOperation {//创建一个重入锁
private Lock lock = new ReentrantLock();//声明等待队列
Condition c1 = lock.newCondition();/* * 等待操作 */
public void await(){
lock.lock();
try {
System.out.println("开始阻塞");
c1.await();
System.out.println("唤醒之后继续执行");} catch (InterruptedException e){
System.out.println("唤醒但是抛出异常了");
e.printStackTrace();} finally {
lock.unlock();}}/* * 唤醒操作 */
public void signal(){
lock.lock();
try {
c1.signal();
System.out.println("唤醒了。。。。。。。。。。。。。。");} finally {
lock.unlock();}}}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
中断测试
public static void main(String[] args){
CarOperation carOperation = new CarOperation();
Thread t1 = new Thread(()->{//等待,挂起线程
carOperation.await();});
t1.start();
try {//模拟其它线程抢占资源执行过程
Thread.sleep(10000);//发出线程中断信号
t1.interrupt();} catch (InterruptedException exception){
exception.printStackTrace();}}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
先唤醒后中断测试
public static void main(String[] args){
CarOperation carOperation = new CarOperation();
Thread t1 = new Thread(()->{
carOperation.await();});
t1.start();
try {
Thread.sleep(10000);//先唤醒线程
carOperation.signal();//后中断
t1.interrupt();} catch (InterruptedException exception){
exception.printStackTrace();}}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
查看reportInterruptAfterWait
//要么抛出异常,要么重新中断。
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();}
1.
2.
3.
4.
5.
6.
7.
8.
以上就是await的全部内容了,我们先来做个简单的总结。
总结
将当前线程封装成node加入等待队列尾部;
彻底释放锁资源,也就是将它的同步队列节点从同步队列队首移除;
如果当前节点不在同步队列中,挂起当前线程;
自旋,直到该线程被中断或者被唤醒移动到同步队列中;
阻塞当前节点,直到它获取到锁资源;
如果你哪个地方存在疑问可以小窗阿Q!
signal
接下来我们再来捋一捋唤醒的过程
public final void signal(){//当前线程是否是锁的持有者,不是的话抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first !=null)//具体的唤醒过程
doSignal(first);}
private void doSignal(Node first){
do {//获取头结点的下一个节点并赋值为头结点
if ((firstWaiter = first.nextWaiter)==null)
lastWaiter =null;//将之前的头节点置为空
first.nextWaiter=null;//将头结点从等待队列转移到AQS队列中,如果转移失败,则寻找下一个节点继续转移
} while (!transferForSignal(first)&&(first = firstWaiter)!=null);}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
首先将等待队列的头结点从等待队列中取出来
然后执行transferForSignal方法进行转移
final boolean transferForSignal(Node node){//将node的waitStatus设置为0,如果设置失败说明node的节点已经不在等待队列中了,返回false
if (!compareAndSetWaitStatus(node, Node.CONDITION,0))
return false;//将node从等待队列转移到AQS队列,并返回node的前驱节点
Node p = enq(node);//获取node前驱节点的状态
int ws = p.waitStatus;//如果该节点是取消状态或者将其设置为唤醒状态失败(说明本身已经是唤醒状态了),所以可以去唤醒node节点所在的线程
if (ws >0||!compareAndSetWaitStatus(p, ws, Node.SIGNAL))//唤醒当前节点
LockSupport.unpark(node.thread);
return true;}