Clojure-JVM上的函数式编程语言(11)引用类型 作者: R. Mark Volkmann
?原帖地址:http://java.ociweb.com/mark/clojure/article.html#ReferenceTypes
?作者:R. Mark Volkmann
?译者:RoySong
?
引用类型(Reference types)????引用类型是针对不可变数据的可变引用。在Clojure中有四种引用类型: Vars, Refs, Atoms和Agents。
它们有很多相同点。
它们都能够包含任意类型的对象。它们都能够被间接引用,在采取deref函数或者@读取器宏来检索它们中包含的对象时。它们都支持验证器(validators)-- 当值被改变时调用的函数,如果新的值是合法有效的,验证器就会返回true。否则验证器会返回false或者抛出一个异常。如果验证器仅仅返回false,则包含"Invalid reference state"信息的IllegalStateException将被抛出。它们都支持监视器(watchers)Agents。当一个被监视的值引用改变时,对应的Agent就被唤醒。更多地细节,参见"Agents"一节。??? 下面的表格简略介绍了四种引用类型的区别,以及能够创建和修改它们的函数。下面表格中的每个函数将在接下来进行
讨论。
Var Ref Atom Agent 用途同步改变到一个单一的、线程本地(thread-local)的值(def name initial-value) (ref initial-value) (atom initial-value) (agent initial-value) 修改 (def name new-value)(alter-var-root
(var name) update-fn args)(set! name new-value)binding form (ref-set ref new-value)dosync(alter ref
update-fn arguments)dosync(commute ref
update-fn arguments)dosync (reset! atom new-value)(compare-and-set! atom current-value new-value)(swap! atom
update-fn arguments) (send agent
update-fn arguments)(send-off agent
update-fn arguments)?
Vars??? Vars是一个引用,它能够拥有一个所有线程共享的根绑定(root binding),也能够在每个线程中拥有不同的值
(thread-local)。
?
??? 创建Var并设置根绑定:
?????接下来的函数支持从一个账户往另一个账户转账。线程通过dosync开始以保证存取动作都发生或者都不发生。
???? 下面的函数支持汇报当前账户的状态。线程通过dosync开始以保证账户汇报的一致性。比如说,它不会汇报
转账中的余额。
???? 上述代码并未处理已开始线程中产生的异常。做为代替,我们在当前线程中为这些异常定义一个异常处理器。
?Atoms??? Atoms提供了一种比联合Ref和STM更简单的机制来更新单一值。它们不会受到线程的影响。
?
????有三个函数能够改变Atom的值:
reset!,compare-and-set!和swap!。?
???
reset!函数能够直接重设Atom的值为新值,而不管原来的值是什么。例子如下:?????为什么这段代码的输出是3?
update-atom函数在reset!函数之前被另一个线程调用了,它获取到Atom的当前值1.然后它进入休眠让
reset!函数有时间去执行。在那之后,Atom的值为3.当update-atom函数调用compare-and-set!来让Atom的值增加时,会失败因为Atom的当前值已经不是原本的1了。这代表着Atom当前的保持值为3.
?
???
swap!函数的第一个参数是目标Atom,然后是一个用来计算Atom新值的函数,然后是任意数量的参数用来传给
计算新值函数。计算新值函数被调用时传入的是目标Atom的当前值以及如果存在的任意数量的参数。swap!函数本质上是
compare-and-set!的一个包装器,除了一个重要的不同。它首先对Atom取值并保存Atom的当前值。然后它调用函数来计算一个新值。最后它调用
compare-and-set!并把第一步保存的值传入。如果compare-and-set!返回false,说明Atom的值不同于调用函数前的值,然后swap!函数会被重复调用直到检查通过为止。这就是那个
重要的不同。上面的代码采用swap!和compare-and-set!来写的例子如下:
???? 为什么输出的结果是4?
在reset!函数调用之前,另一个线程调用了swap!函数。当在swap!函数中调用了update-atom之后,Atom的当前值是1.但是,由于sleep调用,
update-atom函数无法调用结束,直到reset!调用结束,将Atom的值设为3.然后
update-atom函数返回2.在swap!函数将Atom的值设为2之前,它会检查Atom的当前值是否仍然是1.它现在不是,所以
swap!函数再次调用update-atom函数。这次Atom的当前值是3,所以它增加了1并返回4.现在swap!函数验证当前值成功了,并将Atom设值为4.
?
Agents??? Agents被用来运行相互之间无须协调的分离线程中的任务。它对于改变一个作为某个Agent值的单一对象的状态来说
非常有用。这个值通过在单独线程中运行某个”行为“("action")来改变。这个行为是一个函数,接收Agent的当前值
作为参数,并可以接收其他的一些可选参数。对于指定的Agent,一次只能运行一个行为。
?
????agent函数创建了一个新的Agent,如下: