我想知道为什么以下两个调用的行为会有所不同,具体取决于ensure函数是在let内部还是外部引入的:=> "inside let"(def account (ref 1000))(def secured (ref false))(def started (promise))=> #'user/account=> #'user/secured=> #'user/started(defn withdraw [account amount secured] (dosync (let [secured-value (ensure secured)] (deliver started true) (Thread/sleep 5000) (println :started) (when-not secured-value (alter account - amount)) (println :finished))))=> #'user/withdraw(future (withdraw account 500 secured))@started(dosync (ref-set secured true))=> #<core$future_call$reify__6320@7fbde8ed: :pending>=> true:started:finished=> true@account=> 500======== => "outside let"(def account (ref 1000))(def secured (ref false))(def started (promise))=> #'user/account=> #'user/secured=> #'user/started(defn withdraw [account amount secured] (dosync (let [secured-value @secured] (deliver started true) (Thread/sleep 5000) (println :started) (when-not (ensure secured) (alter account - amount)) (println :finished))))=> #'user/withdraw(future (withdraw account 500 secured))@started(dosync (ref-set secured true))=> #<core$future_call$reify__6320@6adadff8: :pending>=> true=> true:started:started:finished@account=> 1000这里的预期语义是,当secured设置为true时,一个人不能提取任何钱。我的理解是,ensure函数将确保secured ref在事务期间没有变化,因此事务重新启动的第二种行为似乎是合理的,但是为什么在第一种情况下行为有所不同?更新:尝试没有踩/睡眠:(def account (ref 1000))(def secured (ref false))(def started (promise))=> #'user/account=> #'user/secured=> #'user/started(defn withdraw [account amount secured] (dosync (let [secured-value (ensure secured)] (deliver started true) ;(Thread/sleep 5000) (println :started) (when-not secured-value (alter account - amount)) (println :finished))))=> #'user/withdraw@account=> 1000(future (withdraw account 500 secured))@started(dosync (ref-set secured true))=> #<core$future_call$reify__6320@6bce0fbf: :pending>:started:finished=> true=> true@account=> 500通过ref-set的更多实验调试(def account (ref 1000))(def secured (ref false))(def started (promise))=> #'user/account=> #'user/secured=> #'user/started(defn withdraw [account amount secured] (dosync (let [secured-value (ensure secured)] (deliver started true) (Thread/sleep 5000) (println :started) (when-not secured-value (alter account - amount)) (println :finished))))=> #'user/withdraw(future (withdraw account 500 secured))@started(dosync do ((println "change started") (ref-set secured true) (println "change done.")))=> #<core$future_call$reify__6320@5b60c101: :pending>=> truechange started...change startedchange started:started:finishedchange done.NullPointerException user/eval2176/fn--2177 (form-init3061788549693294520.clj:3)@account=> 500 最佳答案 首先,我将重申您的问题(以确保我们在同一页面上): 由于并发的(ref-set secured true)调用,我希望在两种情况下withdraw事务都将失败(并重新启动),但是在非允许情况下,我只会观察到重新启动。为什么???这是由于Clojure中STM的一些实现细节所致;具体来说,引用是使用读取器/写入器锁保护的。在第一个示例(使用let)中,先调用(ensure secured),再调用Thread/sleep。由于ensure会在目标ref上获取读锁定,这意味着在5秒钟的睡眠延迟内,您的ref变为只读。由于并发(ref-set secured true)需要对secured进行写锁定才能完成,因此该事务将延迟到withdraw事务完成为止。这就是为什么在这种情况下您不会观察到重新启动的原因-STM实现中的内部锁正在强制写入事务等待读取事务完成。相反,在第二个示例中,在调用(ensure secured)之后调用Thread/sleep。这意味着,直到5秒钟的睡眠延迟之后,事务才知道secured引用需要一个一致的值。由于该事务并没有采取任何措施来保护secured的值(即它没有将其锁定),这意味着任何其他事务都可以在通话。在secured调用之后,使事务知道它需要ensure引用的一致值。在您的示例中,并发(ensure secured)调用更改了该值,因此secured事务必须重新开始。 10-06 13:21