今日のシステムは多くの同時タスクを扱わなければならず、マルチコアCPUの力を活用しなければならない。スレッドでそれを行うのは同期の複雑さのため非常に困難になりうる。Clojureは複数の方法でマルチスレッドプログラミングをシンプルなものにしている。コアとなるデータ構造がイミュータブルなため、簡単にスレッド間で共有できる。しかし、プログラムで状態変更が必要になることはよくある。実用的な言語であるClojureは、状態の変更を認めているが、ロックなどを利用して手動で競合を避けなければならないことから開発者の負担を軽減する一方で、状態を変更する時にはそれが一貫していることを保証するメカニズムを提供している。 dosync, ref, ref-set, alter などを通して提供されている software transactional memoryシステム (STM)は、同期的協調的 な方法でのスレッド間での状態変更の 共有 をサポートしている。 Agent システムは、 非同期的独立 した方法でのスレッド間での状態変更の共有をサポートしている。 Atom システムは、 同期的独立 した方法でのスレッド間での状態変更の共有をサポートしている。 def, binding などを通して提供されている 動的varシステム は、スレッド内での状態変更の隔離をサポートしている。

いずれの場合でも、ClojureはJavaのスレッドシステムを置き換えるのではなく、むしろそれとともに機能する。Clojureの関数はjava.util.concurrent.Callableであるため、Executorフレームワークなどで動作する。

これについてより詳しくは concurrency screencast でカバーされている。

Ref はオブジェクトに対するミュータブルな参照だ。 ref-setalter でトランザクション中に別のオブジェクトを参照するようにすることができ、そのトランザクションは dosync ブロックによって区切られる。トランザクションにおけるrefの変更はすべて起こるか全く起こらないかのどちらかだ。また、トランザクション内でのrefの読み取りは特定の時点での参照の世界のスナップショットを反映している、つまり各トランザクションは他のトランザクションから隔離されている。同一の参照を変更しようとする2つのトランザクションの間で競合が生じた場合、そのうち一方がリトライされる。これらすべてのことは明示的なロックを用いることなく生じる。

この例では、整数を保持するRefのベクターが作られ (refs) 、それぞれのRefをインクリメントする多数の繰り返し処理 (tasks) を実行するためのスレッド (pool) が用意される。これは激しい競合を引き起こすが、正しい結果が得られる。全くロックを使うことなくだ!

(import '(java.util.concurrent Executors))

(defn test-stm [nitems nthreads niters]
  (let [refs  (map ref (repeat nitems 0))
        pool  (Executors/newFixedThreadPool nthreads)
        tasks (map (fn [t]
                      (fn []
                        (dotimes [n niters]
                          (dosync
                            (doseq [r refs]
                              (alter r + 1 t))))))
                   (range nthreads))]
    (doseq [future (.invokeAll pool tasks)]
      (.get future))
    (.shutdown pool)
    (map deref refs)))

(test-stm 10 10 10000)
-> (550000 550000 550000 550000 550000 550000 550000 550000 550000 550000)

典型的な利用方法では、refは永続的でイミュータブルなClojureのコレクションを参照し、複数のトランザクションによる同時の投機的な「変更」を効率的にサポートしている。ミュータブルなオブジェクトをrefに入れるべきではない。

デフォルトではVarは静的だが、 メタデータ とともに定義されたVarのスレッド別の束縛は動的(dynamic)とマークされる。 動的var もまたオブジェクトに対するミュータブルな参照だ。動的varは、 def で作られる(スレッド共有の)ルート束縛を持ち、 set! を使って設定することができるが、それが可能なのは binding を使って新しい格納先にスレッドローカルに束縛された場合だけだ。そうした束縛や束縛に対するその後のあらゆる変更は、スレッド でbindingブロックの動的スコープ内のコードからのみ見ることができる。ネストされた束縛はスタックのルールに従い、制御がbindingブロックを出ると巻き戻される。

(def ^:dynamic *v*)

(defn incv [n] (set! *v* (+ *v* n)))

(defn test-vars [nthreads niters]
  (let [pool (Executors/newFixedThreadPool nthreads)
        tasks (map (fn [t]
                     #(binding [*v* 0]
                        (dotimes [n niters]
                          (incv t))
                        *v*))
                   (range nthreads))]
      (let [ret (.invokeAll pool tasks)]
        (.shutdown pool)
        (map #(.get %) ret))))

(test-vars 10 1000000)
-> (0 1000000 2000000 3000000 4000000 5000000 6000000 7000000 8000000 9000000)
(set! *v* 4)
-> java.lang.IllegalStateException: Can't change/establish root binding of: *v* with set

動的varは、コールスタックの異なる点の間で、介在する呼び出しの引数リストや戻り値を汚すことなくコミュニケーションする方法を提供する。加えて、動的varはコンテキスト指向プログラミングのようなものをサポートする。 defn で定義されたfnはvarに格納されるため、動的に再束縛することが可能だ:

(defn ^:dynamic say [& args]
  (apply str args))

(defn loves [x y]
  (say x " loves " y))

(defn test-rebind []
  (println (loves "ricky" "lucy"))
  (let [say-orig say]
    (binding [say (fn [& args]
                      (println "Logging say")
                      (apply say-orig args))]
      (println (loves "fred" "ethel")))))

(test-rebind)

ricky loves lucy
Logging say
fred loves ethel