转载请注明出处。。。html
接着上一篇的ReentrantLock和condition源码浅析(一),这篇围绕着conditionnode
在这里为了做对比,引入Object类的两个方法,notify和wait方法,这两个方法做用,估计都很清楚,就是一个具备唤醒线程,另外一个具备让线程等待的功能,适用场景,像相似生产者,消费者,那样。ui
这两个方法的注意点,在于必须放在同步块中执行,具体缘由可自行百度或谷歌。执行wait方法后,线程会阻塞,并释放同步代码块的锁(sleep方法会持有锁),notify的方法执行,会唤醒某个线程,可是若是有多个线程执行wait方法阻塞,notify的执行只会唤醒其中某个线程,并不能指定线程唤醒,这时要使用notifyAll才能达到唤醒全部阻塞线程。这样确实有点麻烦,而condition的引入就是为了解决只唤醒执行阻塞的线程。它具备超时做用,即超过某段时间,即会自动唤醒,不会形成一直阻塞。经常使用的阻塞队列就是这个类实现的。this
使用注意点,await和signal必需要在lock方法后执行,若是不执行lock方法就执行await或signal,会出现异常的,具体缘由,稍后分析。spa
在咱们使用该方法时,首先会获取Lock接口的一个实现类,而后调用newCondition类方法,本文以ReentrantLock为例。使用方法以下线程
1 Lock lock = new ReentrantLock(); 2 3 Condition empty = lock.newCondition(); 4 5 // 阻塞线程,并释放锁 6 empty.await(); 7 8 // 唤醒前面阻塞的线程 9 empty.signal();
这里先从await方法(不带参数)开始,代码以下code
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 将当前线程包装成一个Node节点,并节点状态为condition,值为-2 Node node = addConditionWaiter(); // 释放当前线程持有的全部锁 long savedState = fullyRelease(node); int interruptMode = 0; // 判断当前线程是否在同步队列中,即在head->tail队列中,若是不在,那就是还在等待队列中,阻塞当前线程。 while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 当当前线程执行了signal方法会通过这个,即从新将当前线程加入同步队列中 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
前面也说了该方法必定在lock方法内部执行,否则就会抛异常。具体代码在fullRelease(node)方法内,代码以下htm
1 final long fullyRelease(Node node) { 2 boolean failed = true; 3 try { 4 // 获取当前线程状态值,即持有了几个锁 5 long savedState = getState(); 6 // 释放锁,最后最执行到ReentrantLock的tryRelease()方法,该段代码会判断当前线程是与持有锁的线程是同一个线程,若是不是,则抛异常 7 if (release(savedState)) { 8 failed = false; 9 return savedState; 10 } else { 11 throw new IllegalMonitorStateException(); 12 } 13 } finally { 14 if (failed) 15 // 抛异常了,则将此节点状态改成canceled,等待从队列中移除 16 node.waitStatus = Node.CANCELLED; 17 } 18 }
该方法的执行,有这几个步骤blog
一、将当前线程包装成一个node节点,且状态节点为condition,值为-2。接口
二、释放当前线程持有的全部锁,让下一个线程能获取锁。
三、若是条件知足,则阻塞该线程,等待被唤醒
四、若被唤醒,则尝试加入该节点到同步队列中,直到获取锁。
至于该方法的几个其余的重载方法,这里不过多叙述
不同的地方就是改造了while()循环内部代码,重点是使用了LockSupport.parkNanos方法能保证在规定时间,该线程会被唤醒。其余判断就是一些当前线程有没有被中断,有没有达到等待时间等。
该方法用来唤醒执行await方法被阻塞的线程,具体代码以下
1 public final void signal() { 2 // 与await方法同样,若是不在lock方法内执行,则也会抛异常 3 if (!isHeldExclusively()) 4 throw new IllegalMonitorStateException(); 5 Node first = firstWaiter; 6 if (first != null) 7 // 唤醒等待队列中线程 8 doSignal(first); 9 } 10 11 private void doSignal(Node first) { 12 do { 13 if ( (firstWaiter = first.nextWaiter) == null) 14 lastWaiter = null; 15 first.nextWaiter = null; 16 } while (!transferForSignal(first) && 17 (first = firstWaiter) != null); 18 } 19 20 final boolean transferForSignal(Node node) { 21 /* 22 * If cannot change waitStatus, the node has been cancelled. 若是改变node节点状态失败,即该节点被取消了 23 */ 24 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) 25 return false; 26 // 将该节点加入同步队列中,即head->tail队列中 27 Node p = enq(node); 28 int ws = p.waitStatus; 29 // 若是节点被取消,或更改状态失败,则唤醒被阻塞的线程 30 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) 31 LockSupport.unpark(node.thread); 32 return true; 33 } 34
或许有的人会有疑问,执行await方法,有执行LockSupport.park方法来阻塞线程,可是执行signal方法顺利的话,没有代码执行LockSupport.unpark方法,那线程岂不是还一直在阻塞中?
其实呢,signal起的做用并非直接唤醒线程,它的做用是把阻塞的线程移到同步队列中,在上一篇中ReentrantLock和condition源码浅析(一) 博文中有介绍release方法,该方法内部有一个执行unpark方法,
它会去不断的释放知足条件的并被阻塞的线程。
----------------------------------------------------------------------------------------华丽的分界线----------------------------------------------------------------------------------------------------------------------------
以上内容就是我堆condition的了解,如有错误或不足之处,还望指正,谢谢!