管程
一个管程定义了一个数据结构和能够并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。
问题及解决
AQS是一个管程,用于同步不同线程。在实现功能的过程中,需要考虑以下几个问题:
- 在高并发情况下如何确定当前线程是否能够进行后续操作。
- 如果不能进行后续操作,是否需要阻塞;如果阻塞,该如何唤醒。
- 如何对多个线程进行管理,是否需要实现条件等待及如何实现条件等待。
针对以上几个问题,AQS给出了解决方案:
- 使用一个全局的状态,通过判断全局的状态来确定线程是否可以进行后续操作。这个全局的状态为volatile的,通过使用CAS操作来实现对状态的修改,CAS相关的功能由Unsafe类提供。
- AQS维护一个同步队列,在同步队列中,如果线程不允许进行后续操作,则进行阻塞,阻塞操作及唤醒操作由LockSupport调用Unsafe类的方法提供。
- AQS维护条件队列,通过与AQS的配合,完成条件等待与唤醒操作。
结构
AQS
head:始终指向获得了锁的节点,它不会被取消。新的线程获得锁之后,之前获得锁的节点从队列中出队。
tail:负责无锁地实现一个链式结构,采用CAS+轮询的方式。节点的入队操作都在tail节点。
state:AQS当前的状态。
Node
SHARED:表示共享模式。
EXCLUSIVE:表示独占模式。
waitStatus:节点等待状态。CANCELLED=1,表示取消状态。SIGNAL=-1,表示后续节点需要被唤醒。CONDITION=-2,表示线程正处在条件等待状态。PROPAGATE=-3,表示释共享锁时,需要唤醒后续节点。
prev:指向队列中的前一个节点。
next:指向队列中的下一个节点。
thread:把一个节点关联到一个线程。
nextWaiter:指向在条件队列中的下一个正在等待的节点,是给条件队列使用的。值得注意的是条件队列只有在独享状态下使用。
ConditionObject
firstWaiter:指向条件队列的队首节点。
lastWaiter:指向条件队列的对尾节点。
工具类
LockSupport
利用Unsafe直接操作内存来存取对象的能力来设置blockers。Unsafe.objectFieldOffset可以获得某个字段在对象所在内存的offset,有了这个offset,就可以通过对象的引用来找到字段所在的实际内存地址。Thread的parkBlocker属性用来指出当前线程是在哪个对象上阻塞。park方法需要指明锁对象。park方法先setBlocker,标记当前线程是在哪个锁对象上等待,然后调用Unsafe的park方法,当Unsafe的park方法返回时表示已经退出等待,就把blockers设置为null。
Unsafe
Unsafe提供了三类与并发相关的方法:
- CAS方法。如compareAndSwapObject()、compareAndSwapInt()、compareAndSwapLong()。
- 操作条件队列的方法。如park()、unpark()。
- 存取volatile变量的方法。如getObjectVolatile()、putObjectVolatile()、getIntVolatile()、putIntVolatile()等。
操作
获取操作 while(状态不允许获取操作) { if (需要阻塞获取请求) { 如果当前线程不在同步队列中,那么将其插入队列 阻塞当前线程 } else { 返回失败 } } 可能更新同步器状态 如果线程位于同步队列,则将其移出队列 返回成功 释放操作
更新同步器状态 if (新的状态允许某个被阻塞的线程获取成功) { 解除队列中一个或多个线程的阻塞状态 }
方法
扩展
子类扩展时有两种类型,一种是公平的同步器,另一种是非公平的同步器。所谓的非公平,不是说不适用队列来维护阻塞操作,而是说在竞争时,后来的线程可以不考虑在同步队列中的线程而直接竞争资源。同步器竞争失败后,都需要进入AQS的同步队列进行等待,而同步队列是先来先服务的公平的队列。
参考资料
1.《聊聊高并发》