概述
Reetrantlock是Java代码层面提供的锁机制,可做为Synchronized(jvm内置)的替代物,和Synchronized一样都是可重入的。
与Synchronized相比较而言,ReentrantLock有以下优势:支持公平/非公平锁、支持可中断的锁、支持非阻塞的tryLock(可超时)、支持锁条件(Condition)、可跨代码块使用(一个地方加锁,另一个地方解锁),总之比Synchronized更加灵活。但也有缺点,比如锁需要显示解锁、无法充分享用JVM内部性能提升带来的好处等等。
源码分析
Reetrantlock的实现有非公平锁和公平锁两种实现方式,下面逐个分析。
非公平锁
NonfairSync-lock
final void lock() {
//第一次尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {//arg==1
//注意:addWaiter将新创建的node加入了链表尾部。
//第二尝试获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//A
}
上述红字代码A保留了打断状态
非公平锁和公平锁都重度依赖了AbstractQueuedSynchronizer,简称AQS。
AbstractQueuedSynchronizer-addWaiter
/**
* 用传入的mode和当前线程创建node,并入队
// 注意: 新创建的node加入了链表尾部。
* @param 当前线程
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared 互斥和共享两种
* @return 返回新添加的node
*/
private Node addWaiter(Node mode) {
//这里 head tail 的等待队列是双向链表
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//初始化时 tail==null
if (pred != null) {
node.prev = pred;
//compareAndSetTail 的作用是将node设置为tail
//并发状态下有可能set不成功
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//初始化node链表。循环set node为tail直到成功
enq(node);
return node;
}
//初始化node链表。循环set node为tail直到成功
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize·
//初始化head和tail
if (compareAndSetHead(new Node()))
tail = head;
}
else {
//循环set node为tail直到成功
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
这个队列是双向队列,有head和tail 初始化后 head==tail== new Node(), 插入后的节点挂在尾部。 更新 Head 和tail 这2个共享变量都使用了CAS更新。
NonfairSync-tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
Sync-nonfairTryAcquire
//试图获得锁权限
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//状态等于0,可以获取锁
//尝试cas赋值,成功则获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//当前线程多次执行lock。state 增加acquires,这种情况下 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;
}
排队获取锁方法-acquireQueued
//排队的方式获取锁,如果当前节点的前置节点是头节点,证明可以出队,先尝试获取锁,获取不到,进行一次自旋获取锁,这次获取不到后进入阻塞,等待被唤醒
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
//取出当前节点的prev节点
final Node p = node.predecessor();
//如果p是头节点且CAS可以执行成功,则获得锁。
if (p == head && tryAcquire(arg)) {
setHead(node);//注意 这里出队的是 node 的pred节点,就是head节点。
// setHead 执行了 node.prev = null; 加上这段代码 GC回收 p;
p.next = null; // help GC
return interrupted;
}
//A&&B A成功了 B才能执行,一种精简的多个函数执行关系的写法
// 如果应该进入阻塞,设置等待唤醒,进入阻塞。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (RuntimeException ex) {
cancelAcquire(node);
throw ex;
}
}
//如果应该进入阻塞,设置等待唤醒。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//node 节点节点已经设置了等待唤醒。可以安全的阻塞
return true;
if (ws > 0) {
//跳过被取消的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//设置等待唤醒状态
//初始化的node 的waitStatus为 0 or PROPAGATE,标识将要进入被唤醒状态,
//需要重试一次以保障在阻塞前不会获取到锁
//为什么状态的改变被放在前一个节点??
//这样做的原因 自旋? 重试一次
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
这里有个问题: why set前一个节点的状态为等待唤醒?
目前等待队列的结构是 前面的节点的状态是 SIGNAL,表示需要唤醒后面节点代表的线程。不知道为啥如此设计。
//阻塞 并返回当前线程是否被打断
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
释放锁方法unlock
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//检查锁队列头部结点,释放 队列中阻塞的线程。
//注意这里是 队列中 设置前一个节点的状态值的关键。分析 head节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//释放 node下个节点中阻塞的线程。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//被释放了状态需要改变。
if (ws < 0) // 当被打断 ws值 会发生改变。共享锁会改成什么值
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)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
阻塞后被打断执行了取消处理cancelAcquire,节点状态被修改为CANCELLED,CANCELLED==1
why 从后向前找??
考虑极限情况,执行了多个取消处理,取消的顺序是从head开始,如果跳过取消节点获取节点释放,有可能这个节点将要改成取消状态,造成 要被取消的节点被释放,如果从尾部向前查找,发生问题的概率小。
//在发生异常时调用 cancelAcquire
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;
//predNext 是需要断开的节点(predNext有可能是node或)
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
//如果node是tail 移除node
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
//如果是阻塞或即将进入阻塞的节点,移除这个节点。
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 {//此时pred==head 如果是刚加入的节点, 唤醒后续节点,这时节点还没验证能否获得锁就被取消,后续节点有很大可能获取锁,需要唤醒传递。
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
公平锁
public void lock() {
sync.lock();
}
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
公平锁与非公平锁都调用acquireQueued 进入等待队列,它们tryAcquire实现不同。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&//code 1
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//。。。
return false;
}
}
Code1 就是公平锁实现不同的地方,公平锁获取锁的时候会校验等待锁队列中是否有等待的线程,如果有则排队。
公平锁和非公平锁都有互斥模式,Reetrantlock是互斥模式的典型类,Semaphore是共享模式的典型类。
锁从大的分类上来讲有互斥模式和共享模式的区别,公平锁和非公平锁是实现方式的不同。
小结: 非公平锁加锁的过程,经过两次的直接获取锁,如果不成功,进入队列,经过自检,进入阻塞,等待唤醒。
公平锁和非公平锁的区别:非公平锁有两次直接获取锁的机会,而公平锁获取锁的时候会校验等待锁队列中是否有等待的线程,如果有则排队。使用非公平锁,后执行的线程很有可能抢到锁。使用 公平锁,后执行的线程会排队等待先执行的线程。