在并发程序中一般使用锁对共享变量进行互斥访问。在java程序中一般用synchronized关键字来实现线程对共享变量的互斥访问。而从JDK1.5以后java并发大师 Doug Lea 开发了AbstractQueuedSynchronizer(下文用AQS代替)组件,使用原生java代码实现了synchronized语义。我们通过介绍AQS最常用的ReentrantLock锁来介绍AQS。
AQS有两种模式:公平模式和非公平模式(抢占式)。在实现自定义锁时只要按照需求实现公平模式API、非公平模式API或两种模式都实现。ReentrantLock分别实现了公平模式和非公平模式。
在使用公平模式时,AQS会自动生成一个FIFO队列。严格按照线程挂起的顺序获得锁使用权。
猜想:既然锁有被占用和释放两种状态,那么就需要一个state字段来标识锁的状态。果不其然,
以ReentrantLock公平锁为例。
ReentrantLock锁通常使用模式为:
public int show() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
doSomething();
} finally {
lock.unlock();
}
}
查看lock方法源码:
可以发现它只是简单代理了sync的lock方法。
明显的可以看见有两种实现模式。让我们看看公平模式的lock方法实现。
acquire()方法在AQS中实现。
首先看tryAcquire方法。
在AQS中,Doug Lea并没有实现该方法。而需要使用者自己实现。那让我们看看ReentrantLock 公平模式是如何实现该方法的。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); //获得当前执行线程
int c = getState(); //获得当前线程状态
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { //如果当前锁没有线程使用,则当前线程将独占该锁
setExclusiveOwnerThread(current); //设置当锁的拥有者改为当前线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //ReentrantLock是可重用锁,即锁的占用者可以多次重新进入锁
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); //使用CAS修改锁的状态
return true;
}
return false;
}
}
当获得锁的使用权时会返回true,不能获得锁耳朵使用权时会返回false。当返回false时,addWaiter()方法将当前线程加入等待队列中。
等待队列如图所示:
只有head结点指向的结点才能获得锁的使用权。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail; //快速找到尾结点
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) { //使用CAS方式将当前结点链接到尾结点。
pred.next = node;
return node;
}
}
enq(node); //如果CAS操作没能将当前结点链接到尾结点说明有多个线程产生竞争。此时需要使用自旋方式将当前结点链接到尾结点。
return node;
}
将当前线程接入队列后,接着就需要将当前线程挂起。即acquireQueued方法。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); //获得当前结点的前驱结点
if (p == head && tryAcquire(arg)) { //如果前驱是头结点,并成功获取锁状态
setHead(node); //则移除前驱结点,并将当前结点设置为头结点。
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&//否则,检查前驱结点的状态,看当前获取锁失败的线程是否需要挂起。
parkAndCheckInterrupt()) //如果需要挂起,借助LockSupport的静态方法Park挂起当前线程。直到将其唤醒。
interrupted = true;
}
} finally {
if (failed) //如果抛出异常
cancelAcquire(node); //就将当前结点移除等待队列,即请求停止
}
}
到此,线程对锁的竞争告一段落。如果获取锁,则继续运行。否则进入等待队列,线程被挂起。
接下来我们看看锁的释放
同样代理了sync的unlock方法
首先看tryRelease()方法。
同样Doug Lea未实现这个方法。查看ReentrantLock中的实现。
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //当前state状态 - releases值
if (Thread.currentThread() != getExclusiveOwnerThread()) //如果当前线程不是锁的占有者则抛出异常。
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //如果状态为0
free = true;
setExclusiveOwnerThread(null); //就将锁占有者设置为空
}
setState(c); //设置状态
return free;
}
锁释放成功后,找到AQS的头结点并将其唤醒即可。
至此锁的释放操作完成。
只是自己最近研究AQS源码的一点心得,若有不正确的地方请批评指正。