AQS:全称“AbstractQueuedSynchronizer”,直译过来是抽象的队列同步器,一般我们把它叫做AQS,java中大部分并发类都是通过它来实现线程同步。它内部定义了一个变量(volatile int state)和一个等待队列,前者表示加锁状态,后者在多线程情况下争用资源时被阻塞会进入等待队列。
源码解析
volatile int state
volatile 是一个关键字,在并发处理中也经常会用到,后面单独用一篇文章介绍。
(已去除源码中的注释,方便阅读)
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
独占锁(下面会介绍)state初始为0,表示未锁定状态,A线程加锁成功则state+1,这样后面的线程尝试获取锁的时候就会失败,只有当A线程释放锁state=0时,后面的锁才有可能成功获取锁。如果是可重入锁,那么A线程再次获取锁的时候,state会累加,当然,释放锁也要一层一层释放,直到state=0。共享锁state初始为N,多个线程可以同时执行,每个线程执行完会state-1,直到state=0时,再调用主线程。
其中compareAndSetState方法,是原子操作,里面使用了unsafe中的compareAndSwapInt方法,这个Unsafe(不安全)类,听着就不靠谱,为什么这里会用到呢。其实不止在这里,在JUC(java.util.concurrent)包中,尤其是在CAS里大量的用到Unsafe,这是因为Java不能像c语言那样直接访问操作系统底层,但Unsafe类提供了硬件级别的原子操作,而且有很高的效率。那为什么官方不建议开发者使用这个类呢,感觉特地取个这名字(Unsafe)就是吓唬程序员,不要用。因为Unsafe中直接访问内存的方法中使用的内存不受JVM管理,也就不能被GC,需要手动管理,稍有不慎就可能导致内存泄漏。
队列操作
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
将节点插入队列,必要时进行初始化。
里面的for (;;),又是个骚操作,有人会问为啥不用while(true)呢,都是无限循环,而且while(true)看着就很舒服。经常看源码的同学,可能会发现,jdk源码中很多地方都是for(;;),至于为啥用这个,咱们以后有机会再讨论。
Node t = tail;(tail是尾节点,head是头节点)先定义一个尾部节点,如果尾节点是空的,说明队列是空的,这时就需要初始化,源码中特地加了注释// Must initialize,(这种直接在后面加注释的,咱们最好不要学,不符合阿里代码规范,但是Doug Lea是大大佬,阿里代码规范管不住他);初始化以后,接着循环进入else,node.prev = t;将当前节点挂在尾节点后面;(这种node.prev指向上个节点,t.next指向下个节点,这是双向队列的操作,还有单向队列,有兴趣的可以去了解一下)。
看这一个方法里就出现了compareAndSetHead和compareAndSetTail两个CAS操作,点进去发现还是Unsafe实现的,由此可见这个类在多线程中还是很重要的。
AQS中还有很多方法,咱们不一一介绍了,下面介绍几个和资源共享相关的方法。
资源共享方式
AQS定义了两种资源共享方式
- 独占资源,只有一个线程能执行,如ReentrantLock
- 共享资源,多个线程可以同时执行,如Semaphore/CountDownLatch。
独占模式采用tryAcquire-tryRelease实现,共享模式采用tryAcquireShared-tryReleaseShared实现。有一些特殊的锁两种模式都使用,比如ReentrantReadWriteLock(读写锁)。
-
isHeldExclusively():该线程是否独占资源。
protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
-
tryAcquire(int):独占方式。尝试获取资源。
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
-
tryRelease(int):独占方式。尝试释放资源。
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
-
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败; 0 表示成功,但没有剩余
可用资源;正数表示成功,且有剩余资源。protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
-
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回
true,否则返回 false。protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }