dosync
and synchronized
give access to completely different concurrency abstractions.
synchronized
is a way of acquiring and releasing locks. When a thread enters a synchronized
block, it attempts to acquire the appropriate lock; if the lock is currently held by a different thread, the current thread blocks and waits for it to be released. This leads to certain problems, such as the risk of deadlock. The lock is released when the thread leaves the synchronized
block.
dosync
marks a block of code which is to be run in a transaction. Transactions in Clojure are a way of coordinating changes to Refs (objects created with the ref
function); if you need some code to have a consistent view of some pieces of mutable state in Clojure -- and possibly change them -- you put those in Refs and execute your code in a transaction.
A transaction has the interesting property that it will restart if for some reason it cannot commit, up to a certain maximal number of retries (currently hard-coded to be 10000). Among the possible reasons for a transaction being unable to commit are an inability to obtain a consistent view of the world (actually, the relevant Refs -- there is an "adaptive history" facility which makes this less of a problem than it might seem at first glance); simultaneous changes made by other transactions; etc.
A transaction runs no risk of being deadlocked (unless the programmer goes out of their way to introduce a deadlock unrelated to the STM system through Java interop); livelock, on the other hand, is a certain possibility, though it is not very probable. In general, many -- although not all! -- of the intuitions programmers associate with database transactions are valid in the context of STM systems, including that of Clojure.
STM is a huge topic; one excellent resource for learning about Clojure's STM is Mark Volkmann's Software Transactional Memory article. It goes into great depth in discussing Clojure's STM in its final sections, but the beginning can serve as great introductory reading.
As for the snippet you quoted, it's actually not something you would normally want to emulate in production code, since dosync
blocks should almost always be side-effect free; the print
here can be useful for demonstrating the inner working of the STM, but if you wanted a transaction to cause side-effects in real code, you should have it spawn a Clojure Agent for the purpose (which would only execute its task if the transaction successfully commits).