Clojure的并发(八)future、promise和线程

简介:
Clojure 的并发(一) Ref和STM
Clojure 的并发(二)Write Skew分析
Clojure 的并发(三)Atom、缓存和性能
Clojure 的并发(四)Agent深入分析和Actor
Clojure 的并发(五)binding和let
Clojure的并发(六)Agent可以改进的地方
Clojure的并发(七)pmap、pvalues和pcalls
Clojure的并发(八)future、promise和线程 

八、future、promise和线程

1 、Clojure中使用future是启动一个线程,并执行一系列的表达式,当执行完成的时候,线程会被回收:
user =>  (def myfuture (future ( +   1   2 )))
#
' user/myfuture
user =>  @myfuture
3

future接受一个或者多个表达式,并将这些表达式交给一个线程去处理,上面的(+ 1 2)是在另一个线程计算的,返回的future对象可以通过deref或者@宏来阻塞获取计算的结果。

future函数返回的结果可以认为是一个类似java.util.concurrent.Future的对象,因此可以取消:
user =>  (future - cancelled ?  myfuture)
false
user
=>  (future - cancel myfuture)
false

也可以通过谓词future?来判断一个变量是否是future对象:
user =>  (future ?  myfuture)
true

2、Future的实现,future其实是一个宏,它内部是调用future-call函数来执行的:
(defmacro future
  [
&  body] `(future - call (fn []  ~ @body)))
可以看到,是将body包装成一个匿名函数交给future-call执行,future-call接受一个Callable对象:

(defn future - call 
  [
^ Callable f]
  (let [fut (.submit clojure.lang.Agent
/ soloExecutor f)]
    (reify 
     clojure.lang.IDeref 
      (deref [_] (.get fut))
     java.util.concurrent.Future
      (get [_] (.get fut))
      (get [_ timeout unit] (.get fut timeout unit))
      (isCancelled [_] (.isCancelled fut))
      (isDone [_] (.isDone fut))
      (cancel [_ interrupt
? ] (.cancel fut interrupt ? )))))i

将传入的Callable对象f提交给Agent的soloExecuture
final   public   static  ExecutorService soloExecutor  =  Executors.newCachedThreadPool();

执行,返回的future对象赋予fut,接下来是利用clojure 1.2引入的reify定义了一个匿名的数据类型,它有两种protocol:clojure.lang.IDeref和java.utill.concurrent.Future。其中IDeref定义了deref方法,而Future则简单地将一些方法委托给fut对象。protocol你可以理解成java中的接口,这里就是类似多态调用的作用。

这里有个地方值的学习的是,clojure定义了一个future宏,而不是直接让用户使用future-call,这符合使用宏的规则: 避免匿名函数。因为如果让用户使用future-call,用户需要将表达式包装成匿名对象传入,而提供一个宏就方便许多。

3、启动线程的其他方法,在clojure中完全可以采用java的方式去启动一个线程:
user =>  (.start (Thread. #(println  " hello " )))
nil
hello

4、promise用于线程之间的协调通信,当一个promise的值还没有设置的时候,你调用deref或者@想去解引用的时候将被阻塞:
user =>  (def mypromise (promise))
#
' user/mypromise
user =>  @mypromise

在REPL执行上述代码将导致REPL被挂起,这是因为mypromise还没有值,你直接调用了@mypromise去解引用导致主线程阻塞。

如果在调用@宏之前先给promise设置一个值的话就不会阻塞:
user =>  (def mypromise (promise))
#
' user/mypromise
user =>  (deliver mypromise  5 )
#
< AFn$IDeref$db53459f@c0f1ec:  5 >
user
=>  @mypromise               
5

通过调用deliver函数给mypromise传递了一个值,这使得后续的@mypromise直接返回传递的值5。显然promise可以用于不同线程之间的通信和协调。

5、promise的实现:promise的实现非常简单,是基于CountDownLatch做的实现,内部除了关联一个CountDownLatch还关联一个atom用于存储值:
(defn promise
  []
  (let [d (java.util.concurrent.CountDownLatch. 
1 )
        v (atom nil)]
    (reify 
     clojure.lang.IDeref
      (deref [_] (.await d) @v)
     clojure.lang.IFn
      (invoke [
this  x]
        (locking d
          (
if  (pos ?  (.getCount d))
            (
do  (reset !  v x)
                (.countDown d)
                
this )
            (
throw  (IllegalStateException.  " Multiple deliver calls to a promise " ))))))))

d是一个CountDownLatch,v是一个atom,一开始值是nil。返回的promise对象也是通过reify定义的匿名数据类型,他也是有两个protocol,一个是用于deref的IDeref,简单地调用d.await()阻塞等待;另一个是匿名函数,接受两个参数,第一个是promise对象自身,第二个参数是传入的值x,当d的count还大于0的请看下,设置v的值为x,否则抛出异常的多次deliver了。查看下deliver函数,其实就是调用promise对象的匿名函数protocol:
(defn deliver
  {:added 
" 1.1 " }
  [promise val] (promise val))

文章转自庄周梦蝶  ,原文发布时间 2010-08-08
目录
相关文章
|
9月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
448 83
|
9月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
477 83
|
11月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
396 0
|
11月前
|
设计模式 运维 监控
并发设计模式实战系列(4):线程池
需要建立持续的性能剖析(Profiling)和调优机制。通过以上十二个维度的系统化扩展,构建了一个从。设置合理队列容量/拒绝策略。动态扩容/优化任务处理速度。检查线程栈定位热点代码。调整最大用户进程数限制。CPU占用率100%
604 0
|
11月前
|
设计模式 监控 前端开发
并发设计模式实战系列(15):Future/Promise
🌟 大家好,我是摘星!🌟今天为大家带来的是并发设计模式实战系列,第十五章,废话不多说直接开始~
243 0
|
6月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
353 2
|
11月前
|
存储 缓存 安全
JUC并发—11.线程池源码分析
本文主要介绍了线程池的优势和JUC提供的线程池、ThreadPoolExecutor和Excutors创建的线程池、如何设计一个线程池、ThreadPoolExecutor线程池的执行流程、ThreadPoolExecutor的源码分析、如何合理设置线程池参数 + 定制线程池。
JUC并发—11.线程池源码分析
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
273 6
|
8月前
|
前端开发 JavaScript API
一文吃透 Promise 与 async/await,异步编程也能如此简单!建议收藏!
在前端开发中,异步编程至关重要。本文详解了同步与异步的区别,通过生活化例子帮助理解。深入讲解了 Promise 的概念、状态及链式调用,并引入 async/await 这一语法糖,使异步代码更清晰易读。还介绍了多个异步任务的组合处理方式,如 Promise.all 与 Promise.race。掌握这些内容,将大幅提升你的异步编程能力,写出更优雅、易维护的代码,助力开发与面试!
407 0
一文吃透 Promise 与 async/await,异步编程也能如此简单!建议收藏!

热门文章

最新文章