Clojure concurrency programming facilities provides four reference types: Ref, Agent, Atom, Var. Ref is designed for coordinate changes. Coordinate means two or more changes must happen as a whole or nothing should happen. The classic example is the transfer between bank account.

Lets define two bank account :

 
(def account-A (ref 100))
(def account-B (ref 300))
 

A transfer transaction involves changes to the two account at the same time. It must be done in one transaction. A function do this

 
 
(defn tid [] (.getId (Thread/currentThread)))
(defn debug
  [ msg ]
  (print (str msg (apply str (repeat (- 35 (count msg)) \space))  " tid: " (tid)  "\n"))
  (flush))
 
(defn transfer [from to amount id fromname toname]
  (debug (str "id:" id " transfering " amount " from: " fromname " to: " toname))
  (dosync
    (debug (str "id:" id  " in transaction: amount:" amount   " from: " fromname " to: " toname))
    (alter from -  amount)
    (alter to +  amount)
  )
 
)
 

The dosync wrapper around a transaction, there two alter command in this example. The alter function accept a Ref type and a function and parameters list start from second for the function. The first parameter will be current value of the Ref type.

If any of the alter failed, maybe other transaction commit successfully before it, the whole transaction will be retried.

We also add some debug statements, it will show us how each transaction is executed.

Now suppose there are two functions, each function execute a series of transfer operations in a thread. The coordinate changes will make sure no matter how the transactions are processed. The final result will be balanced. The total will be the same as the initial state: 400.

Lets define the two functions.

 
(defn thread-funcA [_]
  (transfer account-A account-B 20 "thread-funcA, 1" "account-A" "account-B")
  (transfer account-A account-B 10 "thread-funcA, 2" "account-A" "account-B")
  (transfer account-A account-B 500 "thread-funcA, 3" "account-A" "account-B")
  (transfer account-B account-A 16 "thread-funcA, 4" "account-B" "account-A")
  (transfer account-B account-A 12 "thread-funcA, 5" "account-B" "account-A")
)
 
(defn thread-funcB [_]
  (transfer account-B account-A 36 "thread-funcB, 1" "account-B" "account-A")
  (transfer account-A account-B 20  "thread-funcB, 2" "account-A" "account-B")
  (transfer account-A account-B 80  "thread-funcB, 3" "account-A" "account-B")
  (transfer account-A account-B 7   "thread-funcB, 4" "account-A" "account-B")  
  (transfer account-B account-A 12  "thread-funcB, 5" "account-B" "account-A")
)
 

We will launch thread on a agent, its just an easy way to a get new thread to do something.

 
(defn launch-thread [fun]
  (let [a  (agent 0)]
    (send-off a fun )
  )
)
 
(defn launch-two-thread []
  (launch-thread thread-funcA)
  (launch-thread thread-funcB)
)
 
(launch-two-thread)
 
(println  (+ @account-A @account-B))
 
 

Here is the output:

 
 
id:thread-funcA, 1 transfering 20 from: account-A to: account-B tid: 16
id:thread-funcB, 1 transfering 36 from: account-B to: account-A tid: 17
id:thread-funcA, 1 in transaction: amount:20 from: account-A to: account-B tid: 16
id:thread-funcB, 1 in transaction: amount:36 from: account-B to: account-A tid: 17
id:thread-funcA, 2 transfering 10 from: account-A to: account-B tid: 16
id:thread-funcA, 2 in transaction: amount:10 from: account-A to: account-B tid: 16
id:thread-funcB, 1 in transaction: amount:36 from: account-B to: account-A tid: 17
id:thread-funcA, 3 transfering 500 from: account-A to: account-B tid: 16
id:thread-funcA, 3 in transaction: amount:500 from: account-A to: account-B tid: 16
id:thread-funcB, 1 in transaction: amount:36 from: account-B to: account-A tid: 17
id:thread-funcA, 4 transfering 16 from: account-B to: account-A tid: 16
id:thread-funcB, 1 in transaction: amount:36 from: account-B to: account-A tid: 17
id:thread-funcA, 4 in transaction: amount:16 from: account-B to: account-A tid: 16
id:thread-funcB, 2 transfering 20 from: account-A to: account-B tid: 17
id:thread-funcA, 4 in transaction: amount:16 from: account-B to: account-A tid: 16
id:thread-funcB, 2 in transaction: amount:20 from: account-A to: account-B tid: 17
id:thread-funcA, 5 transfering 12 from: account-B to: account-A tid: 16
id:thread-funcB, 2 in transaction: amount:20 from: account-A to: account-B tid: 17
id:thread-funcA, 5 in transaction: amount:12 from: account-B to: account-A tid: 16
id:thread-funcB, 3 transfering 80 from: account-A to: account-B tid: 17
id:thread-funcA, 5 in transaction: amount:12 from: account-B to: account-A tid: 16
id:thread-funcB, 3 in transaction: amount:80 from: account-A to: account-B tid: 17 
id:thread-funcB, 3 in transaction: amount:80 from: account-A to: account-B tid: 17 
id:thread-funcB, 4 transfering 7 from: account-A to: account-B tid: 17
id:thread-funcB, 4 in transaction: amount:7 from: account-A to: account-B tid: 17
id:thread-funcB, 5 transfering 12 from: account-B to: account-A tid: 17
id:thread-funcB, 5 in transaction: amount:12 from: account-B to: account-A tid: 17
 
 
 

Now if you print the total amount

 
(println  (+ @account-A @account-B))
 

The result will be 400.