软件事务内存STM #
一、STM简介 #
软件事务内存(Software Transactional Memory,STM)是Clojure并发模型的核心。STM提供了一种无需锁的并发控制机制,通过事务保证状态一致性。
1.1 为什么需要STM #
传统并发问题:
clojure
(def balance (atom 1000))
(defn withdraw [amount]
(let [current @balance]
(Thread/sleep 10)
(reset! balance (- current amount))))
多个线程同时执行可能导致竞态条件。
1.2 STM解决方案 #
clojure
(def balance (ref 1000))
(defn withdraw [amount]
(dosync
(alter balance - amount)))
STM自动处理并发冲突。
二、事务特性 #
2.1 原子性(Atomicity) #
事务要么全部成功,要么全部回滚:
clojure
(def a (ref 0))
(def b (ref 0))
(try
(dosync
(alter a inc)
(throw (ex-info "Error!" {}))
(alter b inc))
(catch Exception e
(println "Caught error")))
[@a @b]
2.2 一致性(Consistency) #
事务保持数据一致性:
clojure
(def accounts
{:checking (ref 1000)
:savings (ref 500)})
(defn transfer [from to amount]
(dosync
(when (>= @from amount)
(alter from - amount)
(alter to + amount))))
(transfer (:checking accounts) (:savings accounts) 200)
[(:checking @accounts) (:savings @accounts)]
2.3 隔离性(Isolation) #
并发事务相互隔离:
clojure
(def x (ref 0))
(def y (ref 0))
(defn update-both []
(dosync
(alter x inc)
(Thread/sleep 100)
(alter y inc)))
(defn read-both []
(dosync [@x @y]))
(future (update-both))
(Thread/sleep 50)
(read-both)
三、MVCC机制 #
3.1 多版本并发控制 #
Clojure STM使用MVCC(Multi-Version Concurrency Control):
clojure
(def counter (ref 0))
(dosync
(let [v1 @counter]
(Thread/sleep 100)
(let [v2 @counter]
[v1 v2])))
每个事务看到一致的数据快照。
3.2 快照隔离 #
clojure
(def a (ref 0))
(def b (ref 0))
(defn snapshot-read []
(dosync
(let [va @a
vb @b]
[va vb])))
(defn update-both []
(dosync
(alter a inc)
(alter b inc)))
事务看到开始时的快照。
3.3 写入冲突检测 #
clojure
(def counter (ref 0))
(defn slow-update []
(dosync
(let [current @counter]
(Thread/sleep 100)
(ref-set counter (inc current)))))
(def futures
(doall
(for [_ (range 5)]
(future (slow-update)))))
(doseq [f futures] @f)
@counter
冲突时事务自动重试。
四、事务操作 #
4.1 alter #
顺序更新,冲突时重试:
clojure
(def counter (ref 0))
(dosync
(alter counter inc))
4.2 commute #
可交换更新,提交时重新计算:
clojure
(def counter (ref 0))
(dosync
(commute counter inc))
commute 适合可交换操作,减少重试。
4.3 ref-set #
直接设置值:
clojure
(def state (ref :idle))
(dosync
(ref-set state :running))
4.4 ensure #
保护读取,防止写入:
clojure
(def a (ref 0))
(def b (ref 0))
(dosync
(ensure a)
(alter b + @a))
五、事务嵌套 #
5.1 嵌套事务合并 #
clojure
(def x (ref 0))
(defn inner []
(dosync
(alter x + 10)))
(defn outer []
(dosync
(alter x inc)
(inner)))
(outer)
@x
嵌套事务合并为一个。
5.2 事务传播 #
clojure
(def a (ref 0))
(def b (ref 0))
(defn update-a []
(dosync
(alter a inc)))
(defn update-b []
(dosync
(alter b inc)))
(defn update-both []
(dosync
(update-a)
(update-b)))
(update-both)
[@a @b]
六、事务隔离级别 #
6.1 快照隔离 #
Clojure STM默认使用快照隔离:
clojure
(def x (ref 0))
(def y (ref 0))
(defn read-consistent []
(dosync
(let [vx @x
vy @y]
(when (not= vx vy)
(println "Inconsistent!" vx vy)))))
(defn update-both []
(dosync
(alter x inc)
(Thread/sleep 50)
(alter y inc)))
6.2 读写冲突 #
clojure
(def counter (ref 0))
(defn read-modify-write []
(dosync
(let [current @counter]
(Thread/sleep 10)
(ref-set counter (inc current)))))
七、性能优化 #
7.1 减少事务时间 #
clojure
(defn slow-transaction []
(dosync
(Thread/sleep 100)
(alter some-ref inc)))
(defn fast-transaction []
(let [value (compute-expensive-value)]
(dosync
(alter some-ref + value))))
7.2 使用commute #
clojure
(def counter (ref 0))
(dosync
(alter counter inc))
(dosync
(commute counter inc))
7.3 减少Ref数量 #
clojure
(def state (ref {:a 0 :b 0 :c 0}))
(dosync
(alter state update :a inc)
(alter state update :b inc))
(def a (ref 0))
(def b (ref 0))
(dosync
(alter a inc)
(alter b inc))
八、实践示例 #
8.1 银行转账系统 #
clojure
(defrecord Account [id owner balance])
(defn make-account [id owner initial-balance]
(ref (->Account id owner initial-balance)))
(defn balance [account]
(:balance @account))
(defn deposit [account amount]
(dosync
(alter account update :balance + amount)))
(defn withdraw [account amount]
(dosync
(let [current (:balance @account)]
(if (>= current amount)
(do
(alter account update :balance - amount)
{:success true})
{:success false :error "Insufficient funds"}))))
(defn transfer [from to amount]
(dosync
(let [result (withdraw from amount)]
(if (:success result)
(do
(deposit to amount)
{:success true})
result))))
(def acc1 (make-account 1 "Alice" 1000))
(def acc2 (make-account 2 "Bob" 500))
(transfer acc1 acc2 200)
[(balance acc1) (balance acc2)]
8.2 库存管理 #
clojure
(def inventory
(ref {:apple 100 :banana 50 :orange 75}))
(defn check-stock [item]
(get @inventory item 0))
(defn reserve-items [items]
(dosync
(let [current @inventory
checks (for [[item qty] items]
[item qty (get current item 0)])]
(if (every? (fn [[_ qty available]]
(>= available qty))
checks)
(do
(doseq [[item qty] items]
(alter inventory update item - qty))
{:success true})
{:success false :error "Insufficient stock"}))))
(reserve-items {:apple 10 :banana 5})
@inventory
8.3 拍卖系统 #
clojure
(def auction
(ref {:highest-bid 0
:highest-bidder nil
:bids []}))
(defn place-bid [bidder amount]
(dosync
(let [current @auction]
(if (> amount (:highest-bid current))
(do
(alter auction
-> (assoc :highest-bid amount)
(assoc :highest-bidder bidder)
(update :bids conj {:bidder bidder :amount amount}))
{:success true :message "Bid accepted"})
{:success false :message "Bid too low"}))))
(defn get-status []
@auction)
(place-bid "Alice" 100)
(place-bid "Bob" 150)
(place-bid "Charlie" 120)
(get-status)
九、STM最佳实践 #
9.1 事务应该简短 #
clojure
(defn good-practice []
(let [data (prepare-data)]
(dosync
(alter ref assoc :key data))))
(defn bad-practice []
(dosync
(let [data (expensive-computation)]
(alter ref assoc :key data))))
9.2 避免IO操作 #
clojure
(defn bad-io []
(dosync
(write-to-disk @ref)
(alter ref inc)))
(defn good-io []
(let [data (dosync @ref)]
(write-to-disk data)))
9.3 选择合适的引用类型 #
| 场景 | 类型 |
|---|---|
| 简单独立状态 | Atom |
| 协调多变量 | Ref |
| 异步更新 | Agent |
十、总结 #
STM核心概念:
| 概念 | 说明 |
|---|---|
| MVCC | 多版本并发控制 |
| 快照隔离 | 事务看到一致快照 |
| 原子性 | 全部成功或全部回滚 |
| 冲突检测 | 自动重试机制 |
关键点:
- STM提供无锁并发控制
- 使用MVCC实现快照隔离
- 事务应该简短,避免IO
commute减少冲突重试- 选择合适的引用类型
并发编程系列完成!接下来学习Java互操作!
最后更新:2026-03-27