A ref holds shared state that must change in coordination with other refs. It is Clojure’s entry point to software transactional memory (STM): wrap related reads and changes in dosync so they belong to one transaction. Unlike atoms or agents, multiple refs can participate in that transaction without explicit locking. section 14.4 section 14.0
(def account-1 (ref 1000)) ; #1
(def account-2 (ref 500))
(defn transfer [amount]
(dosync ; #2
(when (pos? (- @account-1 amount))
(alter account-1 - amount) ; #3
(alter account-2 + amount))
{:account-1 @account-1 :account-2 @account-2}))
(transfer 300) ; #4
;; {:account-1 700, :account-2 800}
The account-transfer example creates two refs, reads the first with @, and uses alter inside dosync to subtract 300 from one balance and add it to the other. The returned balances are 700 and 800. section 14.4
Update or replace a value
Given (def r (ref 0)), (dosync (alter r inc)) returns 1. A later (dosync (ref-set r 2)) returns 2. Use alter to compute a new value with a function; use ref-set to supply the replacement value. section 14.4
Other transaction operations serve different needs: commute is used for concurrent vote counting, while ensure appears where a transaction checks a separate ref before continuing. Transaction code can be retried: a slow reader with default history may restart after another transaction commits. Keep I/O out of it; io! raises an IllegalStateException when I/O is attempted in a transaction. section 14.4
(let [r (ref 0 :min-history 1) ; #1
T1 (future
(dosync
(println "T1 starts")
(Thread/sleep 1000)
(println "T1 history-count"
(ref-history-count r))
@r))
T2 (future
(dosync
(println "T2 starts")
(alter r inc)))] ; #2
[@T1 @T2 @r])
;; T1 starts
;; T2 starts
;; T1 history count 1
;; [0 1 1] ; #3
Setting :min-history to 1 lets the slow reader in this example return the earlier value, 0, while the writer commits 1. Compare that result with the default-history example, where the reader restarts and returns 1. section 14.4
