Concurrency is a powerful but tricky beast. When multiple threads access shared resources, you need a way to prevent chaos. I recently had an issue where double-clicking a button would process a request twice because the database was not updated in time to prevent the process from happening a second time. My first thought was to put something to disable the button from being able to be double-clicked, but after a conversation with my mentor, he brought up the scenario or two different people on two different machines clicking the button at the same time. Enter clojure.core/locking, a built-in tool that makes thread synchronization in Clojure easy and elegant.

What is locking?

The locking macro ensures that only one thread can execute a block of code at a time, protecting shared resources from race conditions. It’s like a guard that says, “Wait your turn!”

How It Works

The syntax for locking is simple:

(locking resource
  ;; <your code>
  ...)
  • resource: The object to lock on. Use any object that all threads can reference consistently.
  • Critical section: Code that modifies shared resources or performs thread-sensitive operations.

A Simple Example

Let’s create a shared counter and safely increment it using locking:

(def counter (atom 0))
(def lock (Object.)) ; Create a lock object

(defn increment-counter []
  (locking lock
    (swap! counter inc)))

; Simulating multiple threads
(doseq [_ (range 10)]
  (future (increment-counter)))

(Thread/sleep 100) ; Wait for threads to finish
(println "Final counter value:" @counter)

Here’s what happens:

  1. A shared lock object ensures only one thread at a time can enter the locking block.
  2. The critical section (swap! counter inc) safely increments the counter.

Why Use locking?

  • Thread Safety: Prevents simultaneous access to shared resources.
  • Simplicity: Minimal boilerplate compared to low-level synchronization primitives.
  • Clojure-Friendly: Plays well with immutable data structures and atoms.