Java并发编程 - 深入剖析ReentrantLock之非公平锁加锁流程(第1篇)
Java并发编程 - 深入剖析ReentrantLock之非公平锁解锁流程(第2篇)
Java语言规范中规定了每个对象都和一个监视器关联,当对象作为锁使用时,未争取到锁的线程会被放入到监视器的EntryList中,而争取到锁的线程在临界区内部通过调用wait可以放弃掉锁,这时候线程被挂起并被放入到监视器的WaitSet中。当调用notify或者notifyAll的时候,WaitSet中的等待线程会被唤醒。也就是说使用synchronized同步,通过监视器WaitSet暂存线程的这种方式使得等待/通知得以实现。
在使用ReentrantLock的时候,通过AQS的协助,未争取到锁的线程也会被放到内部的等待队列中,而在其内部没有看到类似wait、notify和notifyAll类似的方法来实现线程的暂存和唤醒,那么是不是使用ReentrantLock就不能满足需要等待/通知机制的场景呢?
就像使用synchronized需要配合java.lang.Object的wait、notify和notifyAll实现等待通知一样,ReentrantLock也需要其他类或接口的支持,这个接口就是Condition。
下面是Condition接口的定义:
java.lang.util.locks.Condition
void await() throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
long awaitNanos(long nanosTimeout) throws InterruptedException;
void awaitUninterruptibly();
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
买书场景
Java并发编程 - 等待/通知
我们通过使用ReentrantLock和Condition来改写我们之前用wait、notify和notifyAll实现的买书的这个场景。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class BookTradeMutiple {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 作者
Thread writer = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "在写书...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "写好书了...");
condition.signal();
} finally {
lock.unlock();
}
}
}, "作者");
// 买书
Runnable runnable = new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "在等着买书...");
try {
condition.await();// 一直在等
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到书了...");
} finally {
lock.unlock();
}
}
};
new Thread(runnable, "读者1").start();
new Thread(runnable, "读者2").start();
new Thread(runnable, "读者3").start();
new Thread(runnable, "读者4").start();
Thread.sleep(4000);
writer.start();
}
}
使用还是很简单的,现在我们通过这个例子深入源码来分析一下其实现原理。
通过Condition接口的配合,就实现了等待/通知机制。在使用Object类的wait方法的时候,我们上面已经说了,是因为每个对象的监视器暂存了线程,并且线程释放了拥有的锁。上面的代码中同样的只是把调用wait改为await,那么会不会是同样的机制呢?我们先不分析await方法,先来分析Condtion接口。
Condition接口实现
Condition condition = lock.newCondition();
这段代码中我们通过lock创建了一个Condtion对象。
ReentrantLock.java
public Condition newCondition() {
return sync.newCondition();
}
这是通过ReentrantLock的同步器来创建Condition对象。
Sync
final ConditionObject newCondition() {
return new ConditionObject();
}
这里我们看到了一个新的类ConditionObject。这个类是在AbstractQueuedSynchronizer重定义的:
public class ConditionObject implements Condition, java.io.Serializable {
......
}
可以看到它是Condition接口的实现。它的方法很多,我们现在只来看它的属性:
ConditionObject
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
这里我们看到Node的使用,还有属性的命名first和last,我们应该就可以猜到CondtionObject内部拥有一个队列。
await流程分析
现在我们通过调试上面的代码来看看await的工作机制。
上面有4个读者线程,在下面的描述中我们用线程A、B、C、D表示。
通过IDE多线程调试工具依次执行:线程A ->线程B->线程C->线程D。并且每个线程执行await成功后,再开始下一个线程的执行。
建议对比Object类的wait方法来理解。
# 第1步:线程A执行await方法
ConditionObject
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
##第一句:if语句块可以看到await方法可以响应中断,并且会清除中断状态。这和wait方法表现一致。
##第二句:
Node node = addConditionWaiter();
创建条件等待者。
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
初始状态lastWaiter是null,第一个if语句不执行。
Node node = new Node(Thread.currentThread(), Node.CONDITION);
这里创建了一个代表当前线程的节点A。节点A的waitStatus设置的是Node.CONDITION。
这个属性值的含义如下:
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
指示当前的线程在一个条件上等待。
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
t为空,执行if块,这个方法执行完毕的:创建了一个代表当前线程的Node对象,然后firstWaiter和lastWaiter到指向它。
- ##第三句:
int savedState = fullyRelease(node);
这个方法的作用是什么呢? 来看一下:
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
首先通过getState获取当前锁的同步状态,当前线程A获取到了锁,所以这里savedState=1。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
通过调用tryRelease方法释放掉锁,这个和wait方法一致,调用wait方法后也会释放当前线程的锁持有权。
参考之前解锁流程说明中release方法的说明。
此时ReentrantLock的head是空的,第二个if条件不成立,返回true,通过release操作使得当前线程释放掉了锁。
fullyRelease返回的是释放之ReentrantLock的state值,也就是1。
- ##第四句:
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
while条件判断,看下isOnSyncQueue方法:
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
从这个方法命名上看这个方法的作用是:判断Node节点是否在同步队列中。
因为此时A节点在条件队列中,所以isOnSyncQueue返回false。
执行while循环:
LockSupport.park(this);
当前线程被挂起。
总结:调用await方法,线程释放掉了锁的持有权,进入条件队列中并被挂起。
线程A执行完await后,ConditionObject的内部数据如下:
# 第2步:线程B执行await方法
执行流程跟第1步一样,这里就不做分析,只给出线程B执行完await后ConditionObject的内部数据:
# 第3步:线程C执行await方法
线程C执行完await后ConditionObject的内部数据:
# 第4步:线程D执行await方法
signal流程分析
线程A、B、D、D执行完成后,通过IDE调试作者线程的唤醒操作:
condition.signal();
ConditionObject
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
# 第1步:判断当前线程是否是持有锁的线程
ReentrantLock.java
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
# 第2步:判断条件队列是否是空队列, 不是空队列,做doSignal操作
Node first = firstWaiter;
if (first != null)
doSignal(first);
ConditionObject
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
此时first是代表线程A的节点,首先执行:
firstWaiter = first.nextWaiter
将firstWaiter引用指向下一个节点,也就是B节点,此时firstWaiter=B。然后原先的等待头结点的nextWaiter设置为null,即A.nextWaiter=null。
接下来中while的条件语句:
AbstractQueuedSynchronizer.java
**
* Transfers a node from a condition queue onto sync queue.
* Returns true if successful.
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal)
*/
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
transferForSignal——为signal做转移,方法的作用是:将节点从条件队列转移到同步队列。
- @@检查节点是否已经被取消
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
处于条件队列中的节点的waitStatus是是Node.CONDITION,这里通过CAS来将这个值改为0,如果修改不成功,则说明节点的状态已经被改变了。注释中说得很清楚:如果不能改变状态说明节点已经被取消了。
对于已经被取消的节点,没有必要转移到同步队列中。那么transferForSignal返回false。while语句的第一个条件成立,执行第二个条件的判断((first = firstWaiter) != null)。这里的意思其实就是如果想要通知的节点已经取消了那么就继续通知条件队列的新头节点。
do语句块中已经把要通知的节点移出条件队列,队列头节点已经发生改变。
- @@转移到同步队列
Node p = enq(node);
这里注意:
- 因为我们在doSingal的do语句块中,已经重设了firstWaiter,所以这个节点已经从条件队列中移出了。
- enq返回的是node节点的前置节点(在我们这里就是head节点)。
- @@设置前置节点的状态为Node.SIGNAL
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
上面的步骤我们已经把A节点移动到了同步队列中,那么就需要把它前置节点的状态设置为Node.SIGNAL。
if条件判断,如果此时节点的前置节点已经被取消或者重设此节点的前置节点的waitStatus为Node.SIGNAL失败的话就直接唤醒此节点代表的线程,重新进行同步。
节点已经被转移到了同步队列中,但是节点的前置节点已经被取消或者不能设置成Node.SIGNAL。那么就无法通过前置节点来upark,那么唤醒当前节点的线程,做resync操作(继续执行await后面的代码,后面会讲到)。
总结:调用singal方法,会将条件队列的头节点转移到同步队列。之后唤醒流程按照同步队列线程唤醒模式进行。
signal执行完后,ReentrantLock内部数据如下:
通知唤醒和中断唤醒
在"await"的流程分析的时候,已经说明执行await的线程的挂起点:
ConditionObject
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
执行:
LockSupport.park(this);
操作后被挂起。
调用signal方法后,代表线程的节点会被转移到同步队列然后按照同步队列线程唤醒方式唤醒。
不过,因为中断也可以唤醒线程,将中断考虑进去的话情况变得复杂起来。
当signal线程和中断线程不是同一个线程的情况下,就会互相干扰。
这里将唤醒分为两种方式:通知唤醒和中断唤醒。
- 通知唤醒
无中断式的唤醒,通过signal成功节点转移到同步队列,按照同步队列的唤醒方式唤醒线程。 - 中断唤醒
有中断参与的唤醒。singal和中断同时存在,那么就会有执行的先后顺序,这里对中断唤醒分成两类:中断先于通知唤醒和中断后于通知唤醒。
我们回到之前线程A挂起点。
ConditionObject
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
当时线程A是在:
LockSupport.park(this);
这里被挂起的,现在被唤醒了,继续执行后面的代码。
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
- @@终止循环操作
checkInterruptWhileWaiting
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
checkInterruptWhileWaiting方法作用:检查中断,如果"中断在通知之前",返回THROW_IE;"中断在通知之后",返回REINTERRUPT;无中断返回0。
# 第一种情况: 通知唤醒
Thread.interrupted() 条件不满足,返回0。if条件不满足:
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
不会停止循环,继续判断while条件:
while (!isOnSyncQueue(node))
此时节点已被转移到同步队列中了,isOnSyncQueue返回true,while循环结束。
# 第二种情况: 中断唤醒
hread.interrupted() 条件满足,执行transferAfterCancelledWait:
/**
* Transfers node, if necessary, to sync queue after a cancelled wait.
* Returns true if thread was cancelled before being signalled.
*
* @param node the node
* @return true if cancelled before the node was signalled
*/
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
a. 中断先于通知唤醒
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
此时要转移的节点在条件队列中,if条件满足,节点被转移到同步队列。
b. 中断后于通知唤醒
上面的if条件不满足,继续执行:
while (!isOnSyncQueue(node))
Thread.yield();
对于这个可能会有点疑惑,"中断后于通知唤醒"那么节点不是已经转移到同步队列中了吗?
这个考虑的是"正在转移中"的这种情况,唤醒线程正在转移节点,节点处于既不在同步队列又不在条件队列中,那么这时候唤醒执行到了这里发现node还未转移到同步队列,那么它就需要让出CPU的执行权,一直等到通知线程转移节点成功。
正如它的注释所说的那样:
If we lost out to a signal(), then we can't proceed until it finishes its enq(). Cancelling during an incomplete transfer is both rare and transient, so just spin.
中断唤醒,checkInterruptWhileWaiting返回THROW_IE或REINTERRUPT,循环终止。
- @@acquireQueued
while循环退出后,继续执行。
AbstractQueuedSynchronizer.java
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
这个方法我们在讲解ReentrantLock的已经说得很明白了:同步队列中非head节点的节点会被重新挂起;head节点的后继节点自旋式争夺锁的持有权。
也就是说线程从条件队列->同步队列,需要重新获得锁的持有权之后才能跳出await方法,继续执行我们自己代码中await后面的方法。
类比synchronized同步机制的实现,挂起的线程通过notify、notifyAll、interrupt唤醒后,不会直接进入RUNNABLE状态,而是进入BLOCKED状态,等到持有锁之后才会重新进入RUNNABLE状态。
这里又涉及到中断问题,acquireQueued返回到是线程的中断状态,也就是说线程第二次挂起,然后被唤醒是不是通过中断唤醒的。
关于interruptMode的作用,直接看最后:
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
reportInterruptAfterWait
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
THROW_IE:调用signal之前被中断过那么就要抛出InterruptedException。
这个其实可以类比wait方法,线程被interrupt后,wait方法也会catch到然后抛出InterruptedException。
REINTERRUPT:调用signal之后被中断或者是在同步队列中被中断,那么执行自中断,标记一下线程被中断过。
- @@unlinkCancelledWaiters
unlinkCancelledWaiters
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
现在来看这段代码,这段代码还是比较关键的。当节点被从条件队列转移到同步队列的时候,就需要让它脱离条件队列,做法就是设置firstWaiter的指向以及节点本身的nextWaiter,firstWaiter指向它的下一个节点,nextWaiter设置为null。
但是如果是中断使得挂起的线程被唤醒的时候,执行transferAfterCancelledWait,我们可以发现这里只是调用enq方法:
transferAfterCancelledWait
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
并没有对条件队列进行改变,所以转移是完整的,那么unlinkCancelledWaiters这个方法就起作用了,像它注释说那样:清理取消等待的节点。
unlinkCancelledWaiters
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
经过这个方法的调用,节点的转移就完整了。
生产者消费者示例
ProducerConsumerExample
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class Apple {
}
class AppleFactory {
private final int MAX_SIZE = 50;
private final Queue appleList = new LinkedList<>();
private final ReentrantLock lock = new ReentrantLock();
private final Condition fullCondition = lock.newCondition();
private final Condition emptyCondition = lock.newCondition();
// 生产
public void put() {
lock.lock();
try {
while (appleList.size() == MAX_SIZE) {
fullCondition.await();
}
// 生产苹果
System.out.print("生产:当前有" + appleList.size() + "个苹果, ");
appleList.add(new Apple());
System.out.println(Thread.currentThread().getName() + "生产了一个苹果, " + "现在有" + appleList.size() + "个苹果");
emptyCondition.signalAll();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
}
public void take() {
lock.lock();
try {
while (appleList.size() == 0) {
emptyCondition.await();
}
// 消费苹果
System.out.print("消费:当前有" + appleList.size() + "个苹果, ");
appleList.poll();
System.out.print(Thread.currentThread().getName() + "吃了一个苹果, ");
System.out.println("还剩" + appleList.size() + "个苹果");
fullCondition.signalAll();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
AppleFactory appleFactory;
public Producer(AppleFactory appleFactory) {
this.appleFactory = appleFactory;
}
@Override
public void run() {
while (true) {
appleFactory.put();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
AppleFactory appleFactory;
public Consumer(AppleFactory appleFactory) {
this.appleFactory = appleFactory;
}
@Override
public void run() {
while (true) {
appleFactory.take();
}
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
AppleFactory appleFactory = new AppleFactory();
Producer producerTask = new Producer(appleFactory);
Consumer consumerTask = new Consumer(appleFactory);
Thread producer1 = new Thread(producerTask, "生产者@");
Thread producer2 = new Thread(producerTask, "生产者&");
Thread consumer1 = new Thread(consumerTask, "消费者A");
Thread consumer2 = new Thread(consumerTask, "消费者B");
Thread consumer3 = new Thread(consumerTask, "消费者C");
Thread consumer4 = new Thread(consumerTask, "消费者D");
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
consumer3.start();
consumer4.start();
}
}