Condition详解
来源:互联网 发布:定制t恤 淘宝 编辑:程序博客网 时间:2024/06/08 16:03
Condition详解
JDK的Lock是为了替代synchronized同步锁,Condition是为了替代Object的对象监视器锁。
利用Condition,每个对象可以有多个不同的等待集(wait-sets),这样多个线程可以分别在同一个对象的多个等待集上等待,相对于Object的对象监视器锁,Condition提供了更加精细化的锁操作。
Condition(也可以称为Condition queue,条件队列),如果当前线程在某个条件上等待,当该条件满足时,其他线程可以唤醒该线程,该线程可以继续去竞争锁。
Condition和Lock是绑定的,一个Lock可以关联多个Condition,通过Lock.newCondition()方法获得一个Condition。
Condition的使用
举例说明Conditon的使用:
设想我们现在有一个有界的缓存BoundedBuffer,该缓存提供了put和take方法。如果在一个空的BoundedBuffer上take数据,当前线程将会阻塞,直到该缓存有一项数据可用。如果在一个满的BoundedBuffer上put数据,当前线程也会阻塞,直到该缓存有一个空闲位置可用。通过使用两个Condition,可以分别让put和take的线程在不同的条件下等待,这样我们可以优化通知机制,当缓存不空(或者不满)的时候,只通知那些需要take(或者put)数据的线程,避免了无效通知。
看下BoundedBuffer的实现:
import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * <p>文件描述: 有界缓存</p> * * @Author luanmousheng * @Date 17/8/13 上午10:02*/public class BoundedBuffer { private final Lock lock = new ReentrantLock(); //锁lock上的两个条件 //缓存不满条件 private final Condition notFull = lock.newCondition(); //缓存不空条件 private final Condition notEmpty = lock.newCondition(); private final Object[] items = new Object[100]; private int putptr = 0; private int takeptr = 0; private int count = 0; public void put(Object item) throws InterruptedException { lock.lock(); try { while (count == items.length) { //等待缓存不满 notFull.await(); } items[putptr] = item; if (++putptr == items.length) { putptr = 0; } count++; //缓存不空,其他取数据阻塞的线程可以被唤醒了 notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) { //等待缓存不空 notEmpty.await(); } Object res = items[takeptr]; if (++takeptr == items.length) { takeptr = 0; } count--; //缓存不满,其他添加数据阻塞的线程可以被唤醒了 notFull.signal(); return res; } finally { lock.unlock(); } }}
Condition接口定义
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的实现
Condition也叫条件队列,AbstractQueuedSynchronizer实现了该接口。以前学习ReentrantLock的时候也说到了AQS,它是JDK锁和并发的核心实现。AQS维护了一个等待队列,如果锁关联了n个Condition,AQS还会维护n个条件队列,队列的节点都是AQS的内部静态类Node。
AQS的内部类ConditionObject实现了Condition接口,看下ConditionObject的定义:
public class ConditionObject implements Condition, java.io.Serializable { /** 条件队列的第一个节点 */ private transient Node firstWaiter; /** 条件队列的最后一个节点 */ private transient Node lastWaiter; //……}
ConditionObject是通过firstWaiter和lastWaiter维护了一个条件队列。
通常我们会使用条件队列的await和signal方法,那我们就首先学习await的实现原理:
await方法
public final void await() throws InterruptedException { //await方法通过抛出中断异常响应中断 if (Thread.interrupted()) throw new InterruptedException(); //包装当前线程,加入到条件队列的队尾 Node node = addConditionWaiter(); //线程阻塞前释放锁 int savedState = fullyRelease(node); int interruptMode = 0; //判断该节点是否在等待队列上,如果在等待队列,说明该节点已经被其他线程唤醒并移到了等待队列上 while (!isOnSyncQueue(node)) { //不在等待队列,继续在条件队列阻塞 LockSupport.park(this); //interruptMode!=0,说明在该线程唤醒前或者唤醒后被中断过 //响应中断,跳出循环 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);}
await方法主要做了这么几件事:
- 将当前线程加入到条件队列的队尾。
- 释放当前线程已经获取的锁。
- 自旋检查当前节点是否在等待队列,如果不是则继续挂起,否则说明有其他线程唤醒了该线程,尝试去等待队列获取锁。
- 被唤醒或者被中断后,在等待队列中排队获取锁。
await主要流程就是这样,让我们再看下详细实现,看下addConditionWaiter方法:
private Node addConditionWaiter() { Node t = lastWaiter; //如果条件队列队尾的状态不是CONDITION,可能存在已经取消的节点,删除这些节点 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;}
fullyRelease释放当前线程持有的锁。
isOnSyncQueue判断该节点是否在等待队列:
final boolean isOnSyncQueue(Node node) { //结点状态为CONDITION肯定不在等待队列中 if (node.waitStatus == Node.CONDITION || node.prev == null) return false; //存在next节点,说明node肯定在等待队列中 if (node.next != null) return true; //从尾往头遍历查找 return findNodeFromTail(node);}
acquireQueued之前在讲ReentrantLock时已经说明过了,这里不再讲解。
signal方法
signal方法唤醒条件队列上某个节点,并将该节点从条件队列移到等待队列。
public final void signal() { //当前线程持有锁才能唤醒其他线程,否则抛出监视器异常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; //条件队列不为空 if (first != null) //主要逻辑都在这里 doSignal(first);}
这断代码的重点是doSignal方法:
private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null);}
doSignal方法遍历条件队列,直到找到第一个未取消的节点。找到第一个未取消的节点后将该节点移到等待队列,然后退出该方法。
看下transferForSignal方法:
//将节点从条件队列移到等待队列final boolean transferForSignal(Node node) { //原子的更新waitStatus为0 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; //将该节点添加到等待队列 //返回当前节点的前置节点 Node p = enq(node); int ws = p.waitStatus; //如果前置节点已取消或者设置状态为SIGNAL失败,则唤醒当前节点线程 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true;}
该方法将节点从条件队列移到等待队列。
signalAll方法
该方法唤醒条件队列中的所有节点线程,并将这些节点从条件队列移到等待队列。
public final void signalAll() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignalAll(first);}
主要看下doSignalAll方法:
//将first及其之后的节点从条件队列移到等待队列private void doSignalAll(Node first) { lastWaiter = firstWaiter = null; do { Node next = first.nextWaiter; //GC first.nextWaiter = null; //将节点移到等待队列 transferForSignal(first); first = next; //当节点不为空时,转移节点 } while (first != null);}
这断代码逻辑比较简单,遍历条件队列,调用transferForSignal方法,将节点从条件队列移到等待队列。
以上就是条件队列的主要逻辑。
- Condition详解
- Condition详解
- ReentrantLock Condition使用详解
- Condition条件变量详解
- 条件变量(Condition Variable)详解
- 条件变量(Condition Variable)详解
- 条件变量(Condition Variable)详解
- 条件变量(Condition Variable)详解
- 条件变量(condition variable)详解
- 条件变量(Condition Variable)详解
- Condition
- Condition
- Condition
- Condition
- Condition
- Condition
- Condition
- Condition
- lintcode -- 图中两个点之间的路线
- UVA
- 深刻了解jQuery对象和普通DOM对象的区别
- (经典!)深度理解链式前向星(静态邻接表)
- 搭建Ubuntu tftp 服务器和NFS
- Condition详解
- css格式化排版
- 用几种迭代方式打印出TeeeMap中元素
- android 中Service跳转到Activity
- 为何MVC没有被列入23种设计模式之一
- java FileDescriptor介绍
- 数三角形Triangel Counting UVa 11401()
- 如何下载股市所有的股票代码数据
- 输入某年某月某日,是闰年还是平年,判断这一天是这一年的第几天?