从ReentrantLock入手分析
1. 想要使用ReetrantLock给一段代码加锁
ReentrantLock lock = new ReentrantLock();
lock.lock();
直接new一个锁对象调用lock方法先看一下构造方法
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
2.NonfairSync的锁获取实现
Sync是ReentrantLock的一个静态抽象类,NofairSync是这个同步对象的非公平实现。非公平锁指当有多个线程排队获取锁时不会按照线程排队的顺序,找到NofairSync类
加锁流程概括为,当前线程去获取锁,若锁被占用则进入等待队列自旋获取锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Sync继承自AbstractQueuedSynchronizer,Sync提供了ReetrantLock更强大的功能不阻塞的tryLock功能,NonfairSync则实现了Sync的lock方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
方法会使用CAS修改lock对象的state值为1,若lock未被其他线程持有lock的state值为0,CAS成功标记lock被当前线程持有,若lock已被其他线程持有则执行AQS的acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先尝试获取lock,这里的tryAcquire是内置对象sync的方法,NonfairSync最终定位到Sync的nonfairTryAcquire方法。若当前lock未被其他线程持有逻辑和Sync的lock方法一致,使用CAS修改ReetrantLock的state值获取lock,方法返回true。若lock被当前线程持有则将state的值+1,方法返回true。若lock被其他线程持有则方法返回false。
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;
}
这里的nonFair体现在当一个线程来获取lock,若lock空闲线程会直接尝试获取Lock,而不是按照队列顺序获取。
回到acquire当没有获取到lock时会从acquireQueued获取等待队列,在此之前会在Node链表尾部增加一个对象包含当前线程的引用(尾部增加对象使用的都是Unsafe的原子操作线程安全方法),接下来看亿下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())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
重点看一下for循环里的逻辑,会拿到当前线程的对应Node对象,拿到他的preNode,若preNode是等待队列的头结点,尝试获取lock,若此时lock获取成功将当前线程置为头节点,清除Node绑定的thread引用(等待队列Node的head是没有线程映射,head.next才有等待的线程),将Node的watiStatus设置为-1,表示后续线程需要解除阻塞,后续进入队列的node就可以继续自旋获取lock了,这个Node其实也是lock持有者线程的映射,并将前一个空node的next指针置为null以便GC回收,最后退出acuireQueued方法。
若当前线程映射的node不是head的next或者获取lock失败则判断pre的waitStatus状态,当为-1时且当前线程未中断则继续自选获取锁。当前一个线程waitStatus为已终止或跳过则将当前Node的prev往前指向一个Node。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
当前线程获取lock失败,并当前线程在队列中自选获取Lock也失败则会终止当前线程
3.FairSync
公平锁的实现和非公平锁基本一致只是在tryAcquire时有所不同,除非阻塞队列为空,否则直接进入阻塞队列进入循环
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
4.释放锁
显式锁和JAVA内置锁synchronized最大的区别需要手动去释放锁,否则即使线程结束,锁也会阻塞后续获取lock的线程。使用一个显示锁一定要记得使用unlock方式进行释放
lock.unlock();
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
只有线程自己才可以释放自己持有的lock,否则会抛出IllegalMonitorStateException异常。释放锁时会将state-1当state=0时将lock持有线程的引用置为null,因此加几次锁,就需要释放几次。当锁释放成功时会将head的waitStatus的值置为0表示队列里等待线程开始能够通过自选来获取锁了。