引言:ReentrantLock是JUC包中比较经典的同步组件,理解ReentrantLock有利于理解JUC包的实现逻辑。ReentrantLock的特点是互斥和可重入,支持线程公平和非公平获取资源。
一、与Synchronized的区别
ReentrantLock和Synchronized都是Java的悲观锁,那么为什么有了Synchronized,还需要有ReentrantLock,ReentrantLock与Synchronized相比有什么不同?
1.Synchronized是Java的关键字,ReentrantLock是JUC中的同步组件。
2.相比于Synchronized只能阻塞地获取锁,ReentrantLock提供了尝试获取锁,可中断地获取锁,超时获取锁等特性。
3.ReentrantLock的获取锁和释放锁更加灵活。
4.Synchronized是非公平锁,ReentrantLock提供了公平锁的非公平锁的实现。
5.在ReentrantLock中排队的线程处于等待状态,而Synchronized中排队的线程是阻塞状态。
等等。。。。。。
二、AQS
没有获取到锁的线程都去哪里了?他们是怎么被组织和排队的?答案是AQS帮忙了,AQS维护了一个同步队列,并提供了一系列API来管理等待线程。
1. 不响应中断获取
在不响应中断独占模式中,真正实现获取独占锁的函数是acquire,围绕着这个核心函数,可以清楚地理解AQS是怎样维护一个同步队列来有序地获取锁。
/**
* 注释:获取锁的核心函数
*/
public final void acquire(int arg) {
// tryAcquire需要用户自己实现,可以实现公平锁和非公平锁。没有获取到锁的
// 线程通过调用addWaiter加入同步队列,并使用acquireQueued函数不断尝试获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
* 往同步队列添加节点
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 尝试快速入队
Node pred = tail;
if (pred != null) {
// 使得节点的前驱指针有效,并且通过CAS的方式把节点安全加入队列
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) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
/**
* 在同步队列中自旋不断尝试获取锁
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 当前驱节点为头节点并且tryAcquire返回true成功获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
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;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
// 状态为取消的节点出队
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 通过设置前驱节点的状态值来使得当前节点阻塞
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* 阻塞线程
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
/**
* 设置当前节点的状态为取消
*/
private void cancelAcquire(Node node) {
// 如果当前节点为空 直接返回
if (node == null)
return;
node.thread = null;
// 设置当前节点的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 创建前驱节点的后继节点
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
// 如果当前节点是尾节点,直接释放当前节点
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 防止过早通知(响应中断,唤醒前驱节点不是头节点的节点)
// 如果当前节点的前驱节点不是头节点,跳过当前节点,设置前驱节点的后继节点为当前节点的后继节点
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 当前节点的前驱节点是头节点就通知后续节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
2. 释放独占锁
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
三、ReentrantLock重写的同步器方法
1. 非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 简单尝试
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 位于Sync父类
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); // 设置为互斥
return true;
}
}
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;
}
2. 公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && //线程已经被包装成Node,在同步 队列中排队,并且前面没有节点啦!!
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;
}
}
公平锁比非公平锁效率低的原因就是,刚被释放的线程不能获取锁,同步队列头节点的下一个节点才能获取。同步队列头节点的下一个节点对应的线程刚从等待状态被唤醒进入就绪状态,不一定马上被CPU调度。
3. 锁的释放
/**
* 位于Sync父类
*/
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; //返回如果是true,唤醒下一个节点
}