AbstractQueuedSynchronizer是并发的一个关键基础类,它是很多并发类的构成基础,比如ReentrantLock、CountDownLatch等,了解了它其他类就简单明了了。
基础结构
AbstractQueuedSynchronizer(后面简称AQS)继承AbstractOwnableSynchronizer类,AbstractOwnableSynchronizer类只有一个属性“private transient Thread exclusiveOwnerThread;”以及他的get\set方法,可以用来表示当前独占锁持有的线程。
AQS是一个抽象类,并且构造方法是protected的,所以只能继承使用。
AQS包含一个int型的state和一个链表,state用来表示资源状态,比如ReentrantLock用state的0、1来表示锁是否已经被获取,链表用来保存阻塞的线程。所以AQS有三个属性state、head(链表的头节点)、tail(链表的尾节点)。
链表结构是一个AQS的内部类Node,Node类包含属性有:
waitStatus:状态,取消(取消获取锁),挂起(表示包含的线程正在挂起状态),等待等状态;
prev:节点的前一个节点;
next:节点后一个节点
thread:节点对应阻塞的线程,
nextWaiter:用来表示节点类型,是独占锁还是共享锁;
基础方法
AQS有三个final的基础方法getState() 、setState(int newState) 、 compareAndSetState(int expect, int update) ,由于state是用volatile修饰的,所以简单的get\set方法也能得到正确的结果,然后compareAndSetState方法是采用cas方式修改state。
需重写方法
AQS还定义了5个需要子类去重写的方法,方法内部并没有实现任何代码,而是直接抛出UnsupportedOperationException异常,这5个方法为 tryAcquire(int arg)、tryRelease(int arg)、tryAcquireShared(int arg)、tryReleaseShared(int arg)、 isHeldExclusively(),根据名字能够猜到大概意思是获取锁、释放锁等功能。
AQS定义了这个几个方法主要是让各自的实现类根据各自的情况来管理state,比如ReentrantLock中的实现类的tryAcquire方法就是尝试把state的只设置为1,Semaphor中实现的tryAcquireShared则是尝试把state减少一定的数量。不同的实现类实现不同的就能实现不同的阻塞,比如ReentrantLock中state为1表示锁已被获取,Semaphor中state小于0表示信号量已用尽,所以这些实现都是把state当成资源,一旦state达到某种状态,表示资源耗尽,一般都会阻塞线程。
已实现的模板方法
模板方法是AQS中对外的最关键的方法,是扩展其他功能的基础方法,方法提供的最主要的功能就是获取资源或阻塞线程,释放资源唤醒线程,主要几个方法如下:
acquire(int arg) :先调用tryAcquire方法尝试获取资源,如果失败通过acquireQueued方法阻塞线程同时生成一个节点并设置进链表的尾部。
acquireInterruptibly(int arg):与上一个功能类似,在获取资源的过程中会多次判断线程是否中断,如果中断则抛出中断异常。
tryAcquireNanos(int arg, long nanosTimeout):一定时间内没有获取到就不会再获取了。
release(int arg):会调用tryRelease成功后,会唤醒后面节点中的线程;
acquireShared(int arg):调用tryAcquireShared获取共享资源,如果失败会阻塞线程并创建一个共享节点加入到链表中;
acquireSharedInterruptibly(int arg):与上一个方法相似,不过在获取资源与排队的过程中会校验线程的中断状态,如果线程状态会中断则会直接抛出异常;
tryAcquireSharedNanos(int arg, long nanosTimeout):一定时间内没有获取到共享锁则失败;
releaseShared(int arg):先释放资源,在唤醒队列后面的节点中的线程;
这几个方法都是结合上一节中需重写的方法执行的结果来进行下一步操作,比如acquire方法会先调用tryAcquire方法,如果tryAcquire返回true则直接成功,返回false表示获取资源失败,接着就会调用AQS私有方法acquireQueued将线程阻塞并且加到链表中去。这几个方法都是差不多采用这种方式。
通过这种方式由继承类来实现对state的管理,由AQS来管理阻塞线程队列,这样不同的实现类可以实现不同的功能,同时又不用实现管阻塞线程、唤醒线程、线程队列等其他复杂的功能,而只用对state控制就行。
截取了其中两个方法的源码如下图:
不太重要的功能
hasQueuedThreads():是否有阻塞线程;
hasContended():head != null用来判断是否有线程阻塞过。
getFirstQueuedThread():获取最前面的线程,
isQueued(Thread thread):用来判断线程是否在阻塞队列中
apparentlyFirstQueuedIsExclusive():判断最前面的节点是不是独占。
hasQueuedPredecessors():判断当前线程前面是否还有线程在阻塞中
总结
AQS通过一个int型的state来表示资源,再用一个链表来存放阻塞的线程,AQS来管理链表、阻塞线程、唤醒线程,由子类来实现对state的修改和判断。在前面中没有说对值得修改与线程的阻塞、唤醒都是通过Unsafe这个类,这个类中都是本地方法。
AQS实际上就是封装了对线程的一系列操作,在这个基础上要实现其他功能就比较简单了,后面阅读ReentrantLock和Semaphor等源码就会发现是多么的简单了。
Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!