前面已经说过了 ReentrantLock 的四种获取锁的方式,也说过了公平和非公平的区别。
现在来看一下条件 Condition。 Condition 让锁的使用更加的灵活。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Condition 是一个接口, 有两个已知实现类:
AbstractQueuedLongSynchronizer::ConditionObject
AbstractQueuedSynchronizer::ConditionObject
Condition 全部的代码如下:
public interface Condition
{
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
从方法上看,Condition 的功能有2个, await 和 signal
而且这个两个方法的调用都是需要在有锁的环境下进行的。也就是说,如果一个线程
在没有获取锁的情况下就使用这两个方法会抛出异常。具体在哪里抛出异常,需要看源码。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
按照 Condition java doc 写出了简化版的生产者消费者,这个作为源码分析的用户代码
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package cn.rulebreaker.juc.mylock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Lock5App
{
private ReentrantLock lock = new ReentrantLock();
private Condition cdProduct = lock.newCondition();
private Condition cdConsume = lock.newCondition();
private int count;
private void t1()
{
Runnable tProduct = () ->
{
for(int i=0; i<100; ++i) {
lock.lock();
try
{
if(count > 9) {
cdProduct.await();
}
count++;
System.out.println("生产者生产:" + count);
cdConsume.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
Runnable tConsume = () -> {
for (int i=0; i<100; ++i) {
lock.lock();
try {
if(count < 1) {
cdConsume.await();
}
System.out.println("消费产品:" + count);
count--;
cdProduct.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
Thread t1 = new Thread(tProduct, "生产线程");
Thread t2 = new Thread(tConsume, "消费线程");
t1.start();
t2.start();
}
public static void main(String[] args) {
Lock5App lock5App = new Lock5App();
lock5App.t1();
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Condition 的其中一个实现了:AbstractQueuedSynchronizer::ConditionObject
只有一个无参构造方法。而且这个实现类继承父类的方式是公开的,其他除了构造方法外
全部是私有的。
里面有两个属性:
private transient Node firstWaiter;
private transient Node lastWaiter;
像不像 head 和 tail
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//创建一个 条件, 直接会调用到 Condition 的构造方法,构造方法中没有代码。
Condition cdProduct = lock.newCondition();
cdProduct.await();
public final void await() throws InterruptedException {
//如果线程是中断状态,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//创建一个node, 并放到这个条件中的一个单项链表中
Node node = addConditionWaiter(); //-----------1
//彻底释放锁,不管重入了多少次,一次性全部释放
//而且唤醒 head 后面的那个线程起来抢锁了
int savedState = fullyRelease(node); //-----------2
int interruptMode = 0;
while (!isOnSyncQueue(node)) { //-----------3
//这个节点不再一个链表中 ???
//无限阻塞当前线程。下面的代码就先不看了,等解除阻塞以后再来看
LockSupport.park(this); //-----------4
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);
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------1:
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//如果最后一个节点不为空,但是状态不对, 那就检测一下整个列表,
//把状态不对的全部清除
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters(); //-----------1.1
//unlinkCancelledWaiters 内部可能会更新 lastWaiter 的值
t = lastWaiter;
}
//创建一个状态时 CONDITION 的 Node
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//把新创建的 Node 加到这个条件的单向列表中。 并返回这个 Node
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//-----------1.1
/*
* 把链表中(他只是使用 Node.nextWaiter, 并没有 Node.prevWaiter, 所以
* Node 关于 Condition 的实现是单项链表)状态不是 CONDITION 的给清除掉
*/
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
//获取当前节点的下一个节点
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
//当前节点状态不对, 那么当前节点是需要删除的节点
//先把他的后一个节点指向清空。 后一个节点现在就是 next
t.nextWaiter = null;
//如果上一个状态是 CONDITION 节点也是 null。
//说明当前 从头开始找,还没有找到一个状态符合的节点
//所以现在的 firstWaiter 可以直接是 下一个节点了
if (trail == null)
firstWaiter = next;
//存在状态符合的节点,那么把 当前节点的下一个节点
//直接放到符合的那个节点的下一个节点,因为本节点要删除了
else
trail.nextWaiter = next;
//当前节点的下一个节点为空,说明节点没有了。那么最后一个节点就是 trail
if (next == null)
lastWaiter = trail;
}
else
//状态是 CONDITION, trail 持有当前的 节点
//所以在循环中,trail 上次循环状态满足的节点
trail = t;
//当前节点变成直接的下一个节点, 接着继续循环了
t = next;
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------2
//AQS::fullyRelease 外部类 AQS 的方法
final int fullyRelease(Node node) {
boolean failed = true;
try {
//因为已经加锁了,所以 savedState 这个值是大于0的
int savedState = getState();
if (release(savedState)) { //-----------2.1
failed = false;
//释放锁成功, 返回 savedState 。
//因为锁可能被重入,所以这个值 要保管好。因为用户重入几次锁,
//就会 unlock 几次锁。这里如果不记录重入几次, 那么用户调用
//unlock 次数不匹配就异常或者无法彻底释放锁。
return savedState;
} else {
//没有成功释放锁,不正常啊,抛出异常
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
//如果出异常,设置状态为 CANCELLED
node.waitStatus = Node.CANCELLED;
}
}
//-----------2.1
public final boolean release(int arg) {
//尝试释放锁,而且本线程可能重入多次, arg 可能会大于1
//但是经过这个方法,正常应该会把 state 状态清0 ,即释放锁。
if (tryRelease(arg)) { //-----------2.1.1
Node h = head;
//await 释放锁以后, 那些阻塞在获取锁的线程(那个双向链表)
//找到 head 后面的那一个,唤醒他
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//-----------2.1.1 这个方法 ReentrantLock::Sync 复写
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
/*
* 这里有一个异常,如果调用 await 的线程并没有获取锁就会在这抛异常
* 所以,await 方法必须在 有锁的环境下才能调用。
*/
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------3
//AQS
final boolean isOnSyncQueue(Node node) {
//参数 node 就是刚才创建的 CONDITION Node,
//如果锁 这里状态不是 CONDITION 了,难道被取消了
//而且 prev 和 next 这两个数据我们都没使用它啊,
//包括下面的方法 findNodeFromTail 怎么能从 那个线程阻塞列表中找条件节点呢?
//可能这个方法不是为这里使用的。或许不是这个时间点使用的,继续吧
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node); //-----------3.1
}
//-----------3.1
//从尾部开始找 参数 node 是否在双向链表中。
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------4:
LockSupport.park(this);
当前线程调用了 condition.await() 方法, 执行到这里的时候就阻塞在这。
此时需要查看代码如何从这里解除阻塞的。 从上面的代码可以看出,调用 await 的时候
释放了当前线程占有的锁(可能被重入多次),然后就阻塞了,当前线程只是在 条件的单向
列表中添加了节点,但是并没有在阻塞的双向链表添加节点。所以其他线程获取锁,并释放
锁,本线程也是没有可能重新获取锁的(不在阻塞队列)。所以当前线程解除阻塞不是其他
线程释放锁。而应该在条件的signal()。【后续的剧情会再次反转】
================================================================================