导航菜单
首页 » 无极4登录 » 正文

斯柯达昕锐-怎么了解Java并发里的Condition

原文:http://www.importnew.com/9281.html

在java.util.concurrent包中,有两个很特别的东西类,Condition和ReentrantLock,运用过的人都知道,ReentrantLock(重入锁)是jdk的concurrent包供给的一种独占锁的完结。它承继自Dong Lea的 AbstractQueuedSynchronizer(同步器),切当的说是ReentrantLock的一个内部类承继了AbstractQueuedSynchronizer,ReentrantLock只不过是代理了该类的一些办法,或许有人会问为什么要运用内部类在包装一层? 我想是安全的联系,由于AbstractQueuedSynchronizer中有许多办法,还完结了同享锁,Condition(稍候再细说)等功用,假如直接使ReentrantLock承继它,则很简斯柯达昕锐-怎么了解Java并发里的Condition单斯柯达昕锐-怎么了解Java并发里的Condition呈现AbstractQueuedSynchronizer中的API被无用的状况。

言归正传,今日,咱们评论下Condition东西类的完结。

ReentrantLock和Condition的运用办法通常是这样的:

 public static void main(String[] args) {
final ReentrantLock reentrantLock = new ReentrantLock();
final Condition condition = reentrantLock.newCondition();
Thread thread = new Thread((Runnable) () -> {
try {
reentrantLock.lock();
System.out.println("我要等一个新信号" + this);
condition.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("拿到一个信号!!" + this);
reentrantLock.unlock();
}, "waitThread1");
thread.start();
Thread thread1 = new Thread((Run斯柯达昕锐-怎么了解Java并发里的Conditionnable) () -> {
reentrantLock.lock();
System.out.println("我拿到锁了");
try {
Thread.sleep(3000);
}
catch (InterruptedException e) {
e.斯柯达昕锐-怎么了解Java并发里的ConditionprintStackTrace();
}
condition.signalAll();
System.out.println("我发了一个信号!!");
reentrantLock.unlock();
}, "signalThread");
thread1.start();
}

运转后,成果如下:

我要等一个新信号lock.ReentrantLockTest$1@a62fc3
我拿到锁了
我发了一个信号!!
拿到一个信号!!

能够看到,

Condition的履行办法,是当在线程1中调用await办法后,线程1将开释锁,而且将自己熟睡,等候唤醒,

线程2获取到锁后,开端干事,结束后,调用Condition的signal办法,唤醒线程1,线程1康复履行。

以上阐明Condition是一个多线程间和谐通讯的东西类,使得某个,或许某些线程一同等候某个条件(Condition),只要当该条件具有( signal 或许 signalAll办法被带调用)时 ,这些等候线程才会被唤醒,然后从头抢夺锁。

那,它是怎样完结的呢?

首要仍是要理解,reentrantLock.newCondition() 回来的是Condition的一个完结,该类在AbstractQueuedSynchronizer中被完结,叫做newCondition()

public Condition newCondition() { return sync.newCondition(); }

它能够拜访AbstractQueuedSynchronizer中的办法和其他内部类(AbstractQueuedSynchronizer是个抽象类,至于他怎样能拜访,这儿有个很美妙的点,后边我专门用demo阐明 )

现在,咱们一同来看下Condition类的完结,仍是从上面的demo下手,

为了便利书写,我将AbstractQueuedSynchronizer缩写为AQS

当await被调用时,代码如下:

 public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // 将当时线程包装下后,
// 添加到Condition自己保护的一个链表中。
int savedState = fullyRelease(node);// 开释当时线程占有的锁,从demo中看到,
// 调用await前,当时线程是占有锁的
int interruptMode = 0;
while (!isOnSyncQueue(node)) {// 开释结束后,遍历AQS的行列,看当时节点是否在行列中,
// 不在 阐明它还没有竞赛锁的资历,所以持续将自己熟睡。
// 直到它被参加到行列中,聪明的你或许猜到了,
// 没有错,在singal的时分参加不就能够了?
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 被唤醒后,从头开端正式竞赛锁,相同,假如竞赛不到仍是会将自己熟睡,等候唤醒从头开端竞赛。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter 郑仁英!= null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

回到上面的demo,锁被开释后,线程1开端熟睡,这个时分线程由于线程1熟睡时,会唤醒AQS行列中的头结点,所所以线程2会开端竞赛锁,并获取到,等候3秒后,线程2会调用signal办法,"宣布"signal信号,signal办法如下:

 public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter; // firstWaiter为condition自己保护的一个链表的头结点,
// 取出第一个节点后开端唤醒操作
if (first != null)
doSignal(first);
}

阐明下,其实Condition内部保护了等候行列的头结点和尾节点,该行列的作用是寄存等候signal信号的线程,该线程被封装为Node节点后寄存于此。

 public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;

要害的就在于此,咱们知道AQS自己保护的行列是当时等候资源的行列,AQS会在资源被开释后,顺次唤醒行列中早年到后的一切节点,使他们对应的线程康复履行。直到行列为空。

而Condition自己也保护了一个行列,该行列的作用是保护一个等候signal信号的行列,两个行列的作用是不同,事实上,每个线程也只是会一起存在以上两个行列中的一个,流程是这样的:

  1. 线程1调用reentrantLock.lock时,线程被参加到AQS的等候行列中。
  2. 线程1调用await办法被调用时,该线程从AQS中移除,对应操作是锁的开释。
  3. 接着立刻被参加到Condition的等候行列中,以为着该线程需求signal信号。
  4. 线程2,由于线程1开释锁的联系,被唤醒,并判别能够获取锁,所以线程2获取锁,并被参加到AQS的等候行列中。
  5. 线程2调用signal办法,这个时分Condition的等候行列中只要线程1一个节点,所以它被取出来,并被参加到AQS的等候行列中。 留意,这个时分,线程1 并没有被唤醒。
  6. signal办法履行结束,线程2调用reentrantLock.unLock()办法,开释锁。这个时分由于AQS中只要线程1,所以,AQS开释锁后按自始至终的次序唤醒线程时,线程1被唤醒,所以线程1回复履行。
  7. 直到开释所整个进程履行结束。

能够看到,整个协作进程是靠结点在AQS的等候行列和Condition的等候行列中来回移动完结的,Condition作为一个条件类,很好的自己保护了一个等候信号的行列,并在当令的时分将结点参加到AQS的等候行列中来完结的唤醒操作。

看到这儿,signal办法的代码应该不难理解了。

取出面结点,然后doSignal

 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) && // 将老的头结点,参加到AQS的等候行列中
(first = firstWaiter) != null);
}
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;
// 假如该结点的状况为cancel 或许修正waitStatus失利,则直接唤醒。
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}

能够看到,正常状况 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)这个判别是不会为true的,所以,不会在这个时分唤醒该线程。

只要到发送signal信号的线程调用reentrantLock.unlock()后由于它现已被加到AQS的等候行列中,所以才会被唤醒。

总结:

本文从代码的视点阐明晰Condition的完结办法,其间,触及到了AQS的许多操作,比方AQS的等候行列完结独占锁功用,不过,这不是本文评论的要点,等有时机再将AQS的完结独自共享出来。

二维码