Learn how Java locking, memory visibility, concurrent collections, and coordination habits map to Clojure's immutable values, atoms, refs, agents, and JVM interop boundaries.
Java concurrency experience is useful in Clojure, but it should not be copied mechanically. The JVM rules still matter. Threads, memory visibility, blocking queues, atomic classes, monitors, and executor services are still available. What changes is the default design pressure: Clojure asks you to make ordinary data immutable, isolate the few places that really change, and update that state through explicit reference types.
Use this section as a translation layer. Start with the intent behind the Java mechanism, then choose the Clojure shape that expresses the same intent with less mutable surface area.
| Java habit or tool | Usual intent | Clojure-first question |
|---|---|---|
synchronized, ReentrantLock |
Protect a mutable invariant | Can the invariant become an immutable value updated through one atom or a ref transaction? |
volatile and safe publication rules |
Make updates visible across threads | Is the shared value behind an atom, ref, agent, promise, future, or Java interop boundary with clear visibility semantics? |
ConcurrentHashMap |
Share a mutable map safely | Is the map truly shared mutable state, or can a persistent map be returned from a function and swapped atomically? |
BlockingQueue |
Coordinate producers and consumers | Is a JVM queue the right operational primitive, or would an agent, future, promise, or stream abstraction communicate the handoff more clearly? |
The goal is not to prove Clojure is “lock-free” in every situation. The goal is to keep locks, queues, and mutable Java objects at explicit boundaries while the application core remains value-oriented and easier to reason about.