Java中condition的用法,ReentrantLock中的Condition用法

Condition:条件,这个在ReentrantLock中该怎么使用?如果不了解ReentrantLock的可以先看一下ReentrantLock。总体来说可以类比为synchronized、wait和notify之间的用法。

我们还是举例来说明:LinkedBlockingDeque(链接阻塞双端队列,属于JUC包)的put和take方法。先说说看代码大概的思路:1、在put插入数据的时候,当队列容量已经满了,则线程等待队列腾出空间。2、从头部take获取数据的时候,当队列为空的时候,则线程需要等待新数据的插入。那么这两个线程在什么时候等待?怎么进入阻塞状态?又该在什么时候被唤醒?唤醒之后又怎么去重新竞争锁?带着疑问来一步步走。

LinkedBlockingDeque的节点类:

static final class Node {

E item;

Node prev;

Node next;

Node(E x) {

item = x;

}

}

transient Node first; //头节点

transient Node last;//尾节点

private transient int count;//总数

private final int capacity;//容量

final ReentrantLock lock = new ReentrantLock();//可重入锁

private final Condition notEmpty = lock.newCondition();

private final Condition notFull = lock.newCondition();

newCondition实际创建了一个ConditionObject对象,这里的ConditionObject是 AbstractQueuedSynchronizer 的内部类,是Node节点的队列(AbstractQueuedSynchronizer 的Node,不要和LinkedBlockingDeque的Node混淆)

private transient Node firstWaiter;

private transient Node lastWaiter;

这里我们用LinkedBlockingDeque实现队列-先进先出

第一步,入队

public void put(E e) throws InterruptedException {

putLast(e);

}

public void putLast(E e) throws InterruptedException {

if (e == null) throw new NullPointerException();

Node node = new Node(e);//创建队列节点,LinkedBlockingDeque的Node

final ReentrantLock lock = this.lock;

lock.lock();

try {

while (!linkLast(node))

notFull.await();//插入失败,则开始等待--------下面重点分析

} finally {

lock.unlock();

}

private boolean linkLast(Node node) {

if (count >= capacity)//容量满了,返回false

return false;

Node l = last;

node.prev = l;

last = node;//将node插入队列尾部

if (first == null)

first = node;

else

l.next = node;

++count;

notEmpty.signal();

return true;

}

---------------------------------------------------------------------------

//ConditionObject的方法

public final void await() throws InterruptedException {

if (Thread.interrupted())//如果当前线程被标记中断,抛出异常

throw new InterruptedException();

Node node = addConditionWaiter();//加入ConditionObject队列

int savedState = fullyRelease(node);//释放上面putLast时拿到的锁,不释放其他线程拿不到锁

int interruptMode = 0;

while (!isOnSyncQueue(node)) {

LockSupport.park(this);//线程阻塞,线程被唤醒后继续从这里执行,此时node还在ConditionObject队列,并未进入AQS的锁竞争队列

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//判断线程在阻塞期间是否被标记为中断

break;

}

if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//重新获取锁,在signal的时候,node会进入AQS的锁竞争队列

interruptMode = REINTERRUPT;

if (node.nextWaiter != null)

unlinkCancelledWaiters();//清除已经被取消的节点

if (interruptMode != 0)

reportInterruptAfterWait(interruptMode);//是否被中断

}

//ConditionObject的方法

private Node addConditionWaiter() {

Node t = lastWaiter;

if (t != null && t.waitStatus != Node.CONDITION) {

unlinkCancelledWaiters();

t = lastWaiter;

}

Node node = new Node(Thread.currentThread(), Node.CONDITION);//创建AQS的Node队列

if (t == null)

firstWaiter = node;

else

t.nextWaiter = node;

lastWaiter = node;

return node;

}

再来分析下signal:

public final void signal() {

if (!isHeldExclusively())//判断锁的占有线程是否是当前线程

throw new IllegalMonitorStateException();

Node first = firstWaiter;

if (first != null)

doSignal(first);//处理头节点

}

private void doSignal(Node first) {

do {

if ( (firstWaiter = first.nextWaiter) == null)//如果头节点的下一个节点为空,则将尾节点设为空

lastWaiter = null;

first.nextWaiter = null;//将头节点的下一个节点设为空

} while (!transferForSignal(first) &&

(first = firstWaiter) != null);

}

final boolean transferForSignal(Node node) {

//先以CAS将node的waitStatus改为 0

if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))

return false;

Node p = enq(node);//将node插入锁竞争队列尾部。在await中,当线程被唤醒后,会执行acquireQueued(node, savedState),尝试获取锁

int ws = p.waitStatus;

if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))

LockSupport.unpark(node.thread);//唤醒node的所属线程

return true;

}

public E take() throws InterruptedException {

return takeFirst();

}

public E takeFirst() throws InterruptedException {

final ReentrantLock lock = this.lock;

lock.lock();

try {

E x;

while ( (x = unlinkFirst()) == null)

notEmpty.await();//当前线程加入notEmpty队列,等待signal的唤醒

return x;

} finally {

lock.unlock();

}

}

先从take()开始:如果队列为空,执行await()加入notEmpty队列释放当前获取到的锁,此时还在ConditionObject中,然后线程开始阻塞。当有新数据put()进来的时候,执行notEmpty的signal(),将ConditionObject的头节点加入AQS的锁竞争队列,并唤醒头节点所属的线程,开始执行阻塞后的代码,先尝试获取锁,成功则继续执行,失败则阻塞线程继续进入阻塞等待。

如果put()的时候,容量已经满了,这时候会执行 notFull.await(),逻辑跟notEmpty队列一样。在take()执行后会调用notFull.signal(),又唤醒put线程,继续插入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值