Atoms

Atoms 提供了一种比使用Refs&STM更简单的更新当个值的方法。它不受事务的影响

有三个函数可以修改一个Atom的值: reset! , compare-and-set!swap! .

reset! 函数接受两个参数:要设值的Atom以及新值。它设置新的值,而不管你旧的值是什么。看例子:

(def my-atom (atom 1))
(reset! my-atom 2)
(println @my-atom) ; -> 2

compare-and-set! 函数接受三个参数:要被修改的Atom, 上次读取时候的值,新的值。 这个函数在设置新值之前会去读Atom现在的值。如果与上次读的时候的值相等, 那么设置新值并返回true, 否则不设置新值, 返回false。看例子:

(def my-atom (atom 1))

(defn update-atom []
  (let [curr-val @my-atom]
    (println "update-atom: curr-val =" curr-val) ; -> 1
    (Thread/sleep 50) ; give reset! time to run
    (println
      (compare-and-set! my-atom curr-val (inc curr-val))))) ; -> false

(let [thread (Thread. #(update-atom))]
  (.start thread)
  (Thread/sleep 25) ; give thread time to call update-atom
  (reset! my-atom 3) ; happens after update-atom binds curr-val
  (.join thread)) ; wait for thread to finish

(println @my-atom) ; -> 3

为什么最后的结果是 3呢? update-atom 被放在一个单独的线程里面,在 reset! 函数调用之前执行。所以它获取了atom的初始值1(存到变量curr-val里面去了), 然后它sleep了以让 reset! 函数有执行是时间。在那之后,atom的值就变成3了。当 update-atom 函数调用 compare-and-set! 来给这个值加一的时候, 它发现atom的值已经不是它上次读取的那个值了(1), 所以更新失败, atom的值还是3。

swap! 函数接受一个要修改的 Atom, 一个计算Atom新值的函数以及一些额外的参数(如果需要的话)。这个计算Atom新的值的函数会以这个Atom以及一些额外的参数做为输入。swap!函数实际上是对compare-and-set!函数的一个封装,但是有一个显著的不同。 它首先把Atom的当前值存入一个变量,然后调用计算新值的函数来计算新值, 然后再调用compare-and-set!函数来赋值。如果赋值成功的话,那就结束了。如果赋值不成功的话, 那么它会重复这个过程,一直到赋值成功为止。这就是它们的区别:所以上面的代码可以用swap!改写成这样:

(def my-atom (atom 1))

(defn update-atom [curr-val]
  (println "update-atom: curr-val =" curr-val)
  (Thread/sleep 50) ; give reset! time to run
  (inc curr-val))

(let [thread (Thread. #(swap! my-atom update-atom))]
  (.start thread)
  (Thread/sleep 25) ; give swap! time to call update-atom
  (reset! my-atom 3)
  (.join thread)) ; wait for thread to finish

(println @my-atom) ; -> 4

为什么输出变成4了呢?因为swap!会不停的去给curr-val加一直到成功为止。