在上一篇文章中,咱们讲到 Java SDK 并发包里的 Lock 有别于 synchronized 隐式锁的三个特性:可以响应中断、支持超时和非阻塞地获取锁。那今天咱们接着再来详细聊聊 Java SDK 并发包里的 Condition。编程
Condition 实现了管程模型里面的条件变量
在以前咱们详细讲过, Java 语言内置的管程里只有一个条件变量,而 Lock&Condition 实现的管程是支持多个条件变量的,这是两者的一个重要区别。并发
在不少并发场景下,支持多个条件变量可以让咱们的并发程序可读性更好,实现起来也更容易。例如,实现一个阻塞队列,就须要两个条件变量。框架
这里咱们温故知新下前面的内容。异步
public class BlockedQueue<T>{ final Lock lock = new ReentrantLock(); // 条件变量:队列不满 final Condition notFull = lock.newCondition(); // 条件变量:队列不空 final Condition notEmpty = lock.newCondition(); // 入队 void enq(T x) { lock.lock(); try { while (队列已满){ // 等待队列不满 notFull.await(); } // 省略入队操做... // 入队后, 通知可出队 notEmpty.signal(); }finally { lock.unlock(); } } // 出队 void deq(){ lock.lock(); try { while (队列已空){ // 等待队列不空 notEmpty.await(); } // 省略出队操做... // 出队后,通知可入队 notFull.signal(); }finally { lock.unlock(); } } }
不过,这里你须要注意,Lock 和 Condition 实现的管程,线程等待和通知须要调用 await()、signal()、signalAll(),它们的语义和 wait()、notify()、notifyAll() 是相同的, 不要相互使用。源码分析
下面咱们就来看看在知名项目 Dubbo 中,Lock 和 Condition 是怎么用的。不过在开始介绍源码以前,我还先要介绍两个概念:同步和异步。线程
通俗点来说就是调用方是否须要等待结果,若是须要等待结果,就是同步;若是不须要等待结果,就是异步
其实在编程领域,异步的场景仍是挺多的,好比 TCP 协议自己就是异步的,咱们工做中常常用到的 RPC 调用,在 TCP 协议层面,发送完 RPC 请求后,线程是不会等待 RPC 的响应结果的
。可能你会以为奇怪,平时工做中的 RPC 调用大多数都是同步的啊?这是怎么回事呢?code
其实很简单,必定是有人帮你作了异步转同步的事情。例如目前知名的 RPC 框架 Dubbo 就给咱们作了异步转同步的事情,那它是怎么作的呢?下面咱们就来分析一下 Dubbo 的相关源码。队列
对于下面一个简单的 RPC 调用,默认状况下 sayHello() 方法,是个同步方法,也就是说,执行 service.sayHello(“dubbo”) 的时候,线程会停下来等结果。get
DemoService service = 初始化部分省略 String message = service.sayHello("dubbo"); System.out.println(message);
不过为了理清先后关系,仍是有必要分析一下调用 DefaultFuture.get() 以前发生了什么。DubboInvoker 的 108 行调用了 DefaultFuture.get()。这一行先调用了 request(inv, timeout) 方法,这个方法其实就是发送 RPC 请求,以后经过调用 get() 方法等待 RPC 返回结果。同步
public class DubboInvoker{ Result doInvoke(Invocation inv){ // 下面这行就是源码中 108 行 // 为了便于展现,作了修改 return currentClient .request(inv, timeout) .get(); } }
DefaultFuture 这个类是很关键,代码精简以后内容以下。
不过在看代码以前,你仍是有必要重复一下咱们的需求:
不知道你有没有似曾相识的感受,这需求其实就是经典的等待 - 通知机制
吗?这个时候想必你的脑海里应该可以浮现出管程的解决方案了。有了本身的方案以后,咱们再来看看 Dubbo 是怎么实现的。
// 建立锁与条件变量 private final Lock lock = new ReentrantLock(); private final Condition done = lock.newCondition(); // 调用方经过该方法等待结果 Object get(int timeout){ long start = System.nanoTime(); lock.lock(); try { while (!isDone()) { done.await(timeout); long cur=System.nanoTime(); if (isDone() || cur-start > timeout){ break; } } } finally { lock.unlock(); } if (!isDone()) { throw new TimeoutException(); } return returnFromResponse(); } // RPC 结果是否已经返回 boolean isDone() { return response != null; } // RPC 结果返回时调用该方法 private void doReceived(Response res) { lock.lock(); try { response = res; if (done != null) { done.signal(); } } finally { lock.unlock(); } }
调用线程经过调用 get() 方法等待 RPC 返回结果,这个方法里面,你看到的都是熟悉的“面孔”:调用 lock() 获取锁,在 finally 里面调用 unlock() 释放锁;获取锁后,经过经典的在循环中调用 await() 方法来实现等待。
当 RPC 结果返回时,会调用 doReceived() 方法,这个方法里面,调用 lock() 获取锁,在 finally 里面调用 unlock() 释放锁,获取锁后经过调用 signal() 来通知调用线程,结果已经返回,不用继续等待了。
Lock&Condition 是管程的一种实现,因此可否用好 Lock 和 Condition 要看你对管程模型理解得怎么样。管程的技术前面咱们已经专门用了一篇文章作了介绍,你能够结合着来学,理论联系实践,有助于加深理解。
Lock&Condition 实现的管程相对于 synchronized 实现的管程来讲更加灵活、功能也更丰富。
但若是你对实现感兴趣,Java SDK 并发包里锁和条件变量是如何实现的,能够参考《Java 并发编程的艺术》一书的第 5 章《Java 中的锁》,里面详细介绍了实现原理。