其他分享
首页 > 其他分享> > 对AQS的理解

对AQS的理解

作者:互联网

三种让线程等待和唤醒的方法

1)使用Object中的wait()方法让线程等待,使用`Object`中的`notify()`方法唤醒线程

2)使用`JUC`包中`Condition`的`await()`方法让线程等待,使用`signal()`方法唤醒线程

3)`LockSupport`类可以阻塞当前线程以及唤醒指定被阻塞的线程

(`park()`和`unpark()`)

 

传统的synchronized和Lock实现等待唤醒通知的约束

代码Demo

static Object objectLock=new Object();

new Thread(()->{
	synchronized(objectLock){
		objectLock.wait();
	}
},"A").start();

new Thread(()->{
    synchronized(objectLock){
        objectLock.notify();
    }
},"B").start();

 

发生的2类异常

1)wait方法和notify方法,两个都去掉同步代码块??

异常情况:会报IllegalMonitorStateException异常

 

2)将notify放在wait方法前面

异常情况:程序无法执行,无法唤醒

等待中的线程才会被唤醒,否则无法唤醒

 

总结

i)wait和notify方法必须要在同步块或者方法里面,且成对出现使用

ii)先wait后notify

 

LockSupport

是用来创建锁和其他同步类的基本线程阻塞原语

通过park()unpark(thread)方法来实现阻塞和唤醒线程

 

使用了一种名为Permit(许可)来做到阻塞和唤醒线程

每个线程都有一个许可,许可permit只有2个值1和0,默认是0

可以把许可看作一种(0,1)信号量,但许可的累加上限是1

 

park源码

permit默认为0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为0并返回

 

unpark(thread)源码

调用unpark(thread)方法后,会将thread线程的许可permit设置为1(多次调用unpark方法,不会累加,permit还是1)会唤醒thread线程,之前阻塞的LockSupprt.park()方法会立即返回

 

总结

1)无锁块要求

2)没有先唤醒后等待的顺序要求(unpark可以在park之前执行)

 

面试题

1)为什么可以先唤醒线程(unpark)后阻塞线程(park)?

因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺地凭证消费,不会阻塞

 

2)为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证,而调用两次park却需要消费两个凭证,凭证不够,不能放行,则阻塞在那

 

AQS(AbstractQueuedSynchronizer)

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

这个队列就是AQS的抽象表现,它将请求共享资源的线程封装成队列的节点(Node),通过CAS,自旋以及LockSupprt.park()的方式,维护state遍历,使并发达到同步的控制效果

AQS内部体系架构

AQS同步队列的基本结构

AQS=state(同步状态)+CLH队列

包括了头、尾、前、后指针

 

AQS内有Node类:

Node=waitState+前后指针指向

包括int变量waitState(等待状态:等待区其他线程的等待状态)成员变量

 

AQS源码深度解读

ReentrantLock原理:使用Sync锁,Sync又实现了AQS

公平锁和非公平锁:

在创建完公平/非公平锁后,调用lock方法会进行加锁,最终都会调用到acquire方法

!hasQueuedPredecessors()

lock()

当加锁时(公平/不公平锁),先用CAS抢占,若抢占不了,进入acquire()方法

acquire()

tryAcquire()

 

不公平锁的抢占(nonfairTryAcquire)

i)c==0,表示之前抢占的线程A已经用完了,现在线程B可以抢占

ii)当前线程current与要进行抢占的线程如果是同一个线程,则使用重入锁,并且state+1

iii)前面2块都不满足,则抢占不超过,返回false

 

addWaiter

此时node指线程B,第一个线程进入等待队列时,第一个节点并不是该线程,而是系统自己新建的一个虚节点(哨兵节点)prev

这里的for(;;)表示自旋锁

 

acquireQueued()

i)node.predecessor()表示得到前一个节点

第一块表示如果是哨兵节点,并且抢占线程A的位置成功,就成功返回

 

ii)第二块中的shouldParkAfterFailedAcquire()

如果不是哨兵节点, 则将waitStatus置为当前的

表示将哨兵节点的值变为-1,由于自旋锁,再次循环,这次p和node的waitStatus一致。进入下一步,parkAndCheckInterrupt(),用于将线程B挂起

iii)第三块,若failed==true,表明线程B不愿意再等待,直接退出排队

 

unlock()

流程:

sync.release()=>tryRelease(arg)=>unparkSuccessor=>杀回马枪

 

i)

ii)将哨兵节点的waitStatus由0置为-1

将哨兵节点的下一个节点唤醒unpark()

iii)杀回马枪

将哨兵节点的指针指向去掉,这样它就被GC了。哨兵节点紧接着的节点,也就是抢占了之前线程A位置的节点,变成了哨兵节点

最后

三大流程走向

 

标签:AQS,park,理解,线程,unpark,唤醒,节点
来源: https://blog.csdn.net/di_ko/article/details/115515130