最近公司比较忙,整天忙着做项目、做需求,感觉整个人昏昏沉沉的,抬头看天空感觉都是灰色的~~,其实是杭州的天本来就是这个颜色,手动滑稽`~(^o^)/~`。废话不多说,今天突然回忆起面试的时候问到的锁,继而就想起了ReentrantLock这个类,我们知道,JDK1.6已经对synchronized做了很多的优化,性能上已经不比ReentrantLock差了,甚至在竞争激烈的情况下性能还要好很多,那为什么还要研究ReentrantLock呢?首先当然是ReentrantLock有synchronized不能实现的功能,其次就是研究一下ReentrantLock的实现思想,毕竟里面用到了Java并发中高频的AQS。ReentrantLock能做到synchronized做不到的这里不做深究,概括来说就是ReentrantLock可以公平锁,细分条件,可中断。
ReentrantLock中有两个类,FairSync和NonfairSync,分别用于实现公平锁和非公平锁,这里主要研究一下它们的加锁过程,先来看公平锁FairSync
final void lock() { acquire(1); }
纳尼?就一行代码?当然没有这么简单,不管是FairSync还是NonfairSync都是继承自ReentrantLock里面的一个类Sync,而Sync又是一个AQS的实现(这里不对AQS作展开,想要了解的同学可以自行google或者baidu)。看一下Sync的acquire方法,并没有重写,用的父类方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
其实AQS的实现类只要实现tryAcquire方法就可以了,这样调用acquire方法就能够有不同的表现。先从方法的表面意思来猜测一下,tryAcquire(arg)就是尝试获得锁,false应该是获得失败,所以if内语句执行的第一个条件就是获得锁失败;acquireQueued(addWaiter(Node.EXCLUSIVE), arg))这个应该是加入等待的队列,Node.EXCLUSIVE翻译过来是独占,那就是独占模式加队列?先这么理解吧,那如果条件成立会做什么呢?
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
其实就是当前线程尝试中断(置中断标志位),那总结一下:首先当前线程尝试获得锁,如果失败就尝试加入等待的队列(以独占模式,先不管这事啥意思),这里补充一下,acquireQueued中如果线程被中断是会返回true的,所以线程不中断就会停在acquireQueued方法,里面也会再次调用tryAcquire方法的。好了,猜测终归是猜测(其实也是看了实现的),先看看tryAcquire方法,FairSync中的
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// c记录的是当前同步器的状态,state是AQS的变量,是个volatile变量
int c = getState();
// 等于0代表没有其他线程获得锁
if (c == 0) {
// hasQueuedPredecessors()会判断当前等待的队列中是否有排在前面的线程,因为是公平锁,所以要先到先得
// 这里用CAS的原因是hasQueuedPredecessors()方法判断的只是当时的情况,返回后可能有其他线程已经抢占锁了
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 锁重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
// 不能超过Integer的最大值
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 其他情况获取锁失败
return false;
}
代码中我都加了注释,相对来说tryAcquire方法还是比较好理解的,再来看看addWaiter(Node.EXCLUSIVE)方法
private Node addWaiter(Node mode) {
// 构造当前线程的Node,Node是等待队列的基本单位
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接在原来尾部node的后面,CAS成功则建立双向链表,然后返回当前node
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 不停尝试将当前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;
}
}
}
}
不管怎样,addWaiter(Node.EXCLUSIVE)方法返回的都是代表当前线程的node,然后又作为入参调用了acquireQueued()方法,来看看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法做了什么
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 如果当前node紧跟在head之后,则再尝试获取锁,head从前面的过程来看其实是一个没有意义的new Node(),获取成功当然就返回了
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);
}
}
总结:首先尝试获得锁,获得失败就不停地把自己加到队列末尾,然后就不停的判断是否轮到自己来获得锁了,如果获得成功就会把自己从队列中移除,这期间如果出现线程中断则取消获取锁的操作。
因为FairSync和NonfairSync的unlock()方法都是相同的继承自父类,所以最后统一分析,先讲NonfairSync的lock()方法,这里多了一个if的分支,为什么这样就是不公平了呢?因为这里第一步执行的就是:如果没有其他线程获得锁那就我来吧,它才不管你是不是有线程在队列中排队。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
非公平锁的tryAcquire()方法和公平锁是有区别的,实现了非公平的策略
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
乍看nonfairTryAcquire()方法,明明和公平锁的tryAcquire()方法一样嘛,搞飞机呢,重写了一遍。但其实这里还是稍微有点不同的,就是在没有线程获得锁的情况下,非公平策略是不会去判断是否队列中有其他node在自己前面的,直接就是一个CAS,成则成已,不成我也认了,排队这种事,不存在的,嘿嘿。
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;
}
最后看一下两种锁的释放,unlock()方法
public void unlock() {
sync.release(1);
}
又是简单粗暴。。。继续看Sync的release()方法
public final boolean release(int arg) {
// 尝试释放锁成功,唤醒本node的接班node,尝试失败返回false,但是外层并没有用到返回值
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
锁几次就要释放几次
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 没获得锁的线程是没有资格释放锁的
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// c=0代表全部释放了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
// 未全部释放返回false则外层不会去唤醒接班的node
return free;
}
ReentrantLock关于锁的获得和释放差不多就是这个样子,其实还是有一些细节没有很深入的讲的,主要是对node等待队列的一些操作,包括阻塞、唤醒接班人等,但这就涉及到JDK这么设计的思想了,感觉有点偏离了锁实现这个概念,理解ReentrantLock大可以跳过这部分。而且我主要讲的是独占模式的锁,等有空把共享模式的也补充一下。
本文由博客一文多发平台 OpenWrite 发布!