ReentrantLock(以下简称RL)是JDK5之后推出的互斥锁,实现了java.util.concurrent.locks.Lock接口,功能和synchronized关键字几乎一样,但是写法上有区别,而且提供了几个更加灵活的API:
RL虽然并未继承AQS(AbstractQueuedSynchronizer),但是其内部类Sync继承了AQS,RL的API实现也基本是由sync提供的。AQS是JUC中比较重要的一个基类,它也是很多实现类的模板类,包括RL在内的许多同步器(Semaphore,CountDownLatch、线程池等)都是复用其中的代码完成同步功能的。AQS的主要数据结构是等待队列(双向链表)以及state计数器。
有个很有意思的点就是:AQS虽然被声明为抽象类但是它却没有一个抽象方法,它只有五个抛出UnsupportedOperationException的方法,这几个方法也正是需要子类覆写的方法。这里之所以没有定义成abstract,是因为独占模式(例如RL)下只用实现tryAcquire-tryRelease,而共享模式(例如Semaphore)下只用实现tryAcquireShared-tryReleaseShared。如果都定义成abstract,那么每个模式也要去实现另一模式下的接口。
下面还是通过一个经典的生产者消费者的例子来看下RL的API使用方式,与【多线程与并发】synchronized同步锁这篇文章相比,除了锁的方式不同外,队列我采用了链表的形式。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerAndConsumerWithRL {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
ProductQueue queue = new ProductQueue(5);
for (int i = 0; i < 5; i++) {
pool.execute(new Producer(3, queue));
}
for (int i = 0; i < 3; i++) {
pool.execute(new Consumer(5, queue));
}
pool.shutdown();
}
private static class Product {
private static int count = 0;
private int id = count++;
private Product next;
@Override
public String toString() {
return "product(id=" + id + ")";
}
public void setNext(Product next) {
this.next = next;
}
public Product getNext() {
return next;
}
}
private static class ProductQueue {
private int maxSize;
private int currentSize;
private Product head;
private Product tail;
private ReentrantLock lock = new ReentrantLock();
// 生产者等待条件队列
private Condition producerCondition = lock.newCondition();
// 消费者等待条件队列
private Condition consumerCondition = lock.newCondition();
ProductQueue(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("size must > 0");
}
this.maxSize = maxSize;
}
public void put(Product product) {
lock.lock();
try {
// 队列满时生产者进入生产者等待队列,并且自动释放锁。这个与Object#wait()相同
while (currentSize == maxSize) {
producerCondition.await();
}
// 队列为空
if (head == null && tail == null) {
head = product;
} else {
tail.setNext(product);
}
tail = product;
currentSize++;
// 生产后通知所有消费等待者,而Object#notifyAll()只能通知所有的线程
consumerCondition.signalAll();
System.out.printf("%s has been put%n", product);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// unlock通常放在finally中,确保锁一定会释放
lock.unlock();
}
}
public Product take() {
lock.lock();
try {
// 队列为空时,消费者进入消费者等待队列,并且释放锁
while (head == null && tail == null) {
consumerCondition.await();
}
Product currentHead = head;
Product nextHead = head.getNext();
currentHead.setNext(null);
head = nextHead;
// 队列只有一个元素时
if (nextHead == null) {
tail = null;
}
currentSize--;
// 消费后通知所有生产等待者
producerCondition.signalAll();
System.out.printf("%s has been taken%n", currentHead);
return currentHead;
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException("interrupted");
} finally {
lock.unlock();
}
}
}
private static class Producer implements Runnable {
private static int count;
private int id = count++;
private int num;
private ProductQueue queue;
Producer(int num, ProductQueue queue) {
this.num = num;
this.queue = queue;
System.out.printf("producer%d produces %d products%n", id, num);
}
@Override
public void run() {
for (int i = 0; i < num; i++) {
queue.put(new Product());
}
}
}
private static class Consumer implements Runnable {
private static int count;
private int id = count++;
private int num;
private ProductQueue queue;
Consumer(int num, ProductQueue queue) {
this.num = num;
this.queue = queue;
System.out.printf("consumer%d consumes %d products%n", id, num);
}
@Override
public void run() {
for (int i = 0; i < num; i++) {
queue.take();
}
}
}
}
下面通过RL的加解锁来窥探一下AQS的结构及其关键源码
从活动图中可以看到,RL的lock方法是由NonFairSync#lock()实现的,它主要做了两件事:
// java.util.concurrent.locks.ReentrantLock#lock
public void lock() {
sync.lock();
}
// java.util.concurrent.locks.ReentrantLock.Sync#lock
abstract void lock();
// java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
final void lock() {
if (compareAndSetState(0, 1)) // 第一次获取锁
setExclusiveOwnerThread(Thread.currentThread());
else // 1.获取锁失败,acquire中还会再次cas争抢锁 2.锁重入
acquire(1); // AQS关键方法
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#compareAndSetState
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// java.util.concurrent.locks.AbstractOwnableSynchronizer#setExclusiveOwnerThread
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
可以看到上面非公平锁的场景下线程一上来不管三七二十一就去通过cas的方式争抢锁,而不管这个锁是否有其他线程正在排队,此外如果争抢失败,它后续还会通过nonfairTryAcquire方法再去cas争抢锁。这就好比一个人去售票窗口买票,不管这个窗口是否有人排队他都会和队头的人去争抢两次买票机会,如果没抢到机会才会排到队尾。而公平锁的话就好比一个守秩序的人如果看到窗口没人才会去争抢买票,而如果有人排队就会排到队尾去了。代码如下
// java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
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()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
和场景一相比差别主要在ReentrantLock.NonfairSync#lock这个方法上,在本节场景下走的是else分支中的acquire()方法,该方法最终会调用nonfairTryAcquire()中的锁重入分支。
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
// 如果尝试获取锁失败,就去排队
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 第一次获取锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 锁重入,state+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 未获取到锁
return false;
}
场景二中nonfairTryAcquire()能够通过锁重入分支返回true,但是在本节这个场景下该方法由于未抢到锁而返回了false,所以需要接着执行图中的②和③。由于cas操作都依赖于Unsafe类,出于简化图片的目的我就没有标注出来,有需要可以参考场景一中的图片。
方法②实现的主要功能是将当前线程封装成一个节点(Node)然后插入等待队列的队尾。Node作为AQS的内部类用来表示节点,它内部保存了当前线程,并且有如下两种模式,RL毫无疑问使用的是模式2
以及五种状态 :
//java.util.concurrent.locks.AbstractQueuedSynchronizer
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 前面已经有等待的线程
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 第一个进入等待的线程,或排到队尾失败
enq(node);
return node;
}
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;
}
}
}
}
方法③主要是线程出入队列的管理,当线程进入等待队列后会通过cas的方式将前驱结点的status标记为signal然后再进入等待状态
//java.util.concurrent.locks.AbstractQueuedSynchronizer
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())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 前驱结点的状态是signal,当前线程可以进入等待状态
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
//如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。出现这种情况有两种可能:
1. 前驱结点还在执行,自己第一次进入该方法
2. 前驱结点执行完毕了,但是有其他线程通过非公平锁争抢到执行机会
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // LockSupport最终也是利用unsafe的本地方法实现线程阻塞等待
return Thread.interrupted(); // 如果被唤醒,查看自己是不是被中断的。
}
从上图中可以看出RL的unlock()方法的实现是由AQS的release()提供的,它主要实现两个功能:
//java.util.concurrent.locks.ReentrantLock#unlock
public void unlock() {
sync.release(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 当前线程完全释放了锁(无重入)
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
private void unparkSuccessor(Node node) {
// 将节点状态设为初始值
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 后继节点不存在或者已经取消等待,则从队尾逆向遍历队列找到第一个等待节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) // 这个for循环用的很巧妙,一般来说可能会先想到使用while break的方式。
if (t.waitStatus <= 0)
s = t;
}
// 若存在后继等待节点则唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
Java并发之AQS详解
从ReentrantLock的实现看AQS的原理及应用
深入理解java虚拟机