Lock-free Atoms in F# and Java

Lock-free mutation of state using CAS (Compare-And-Swap) operations is a powerful technique for concurrent programming. In my previous post, Immutable Beans, I explored similar ideas. Here, I want to show how the “atom” concept from Clojure can be implemented in Java and F# using AtomicReference and Interlocked, respectively, to provide lock-free transactional memory for a single value.

Most programs really only need one variable. Just one state – the state. That state can be complex: a tree with many branches, maps, or whatever suits your data. But in the end, it’s all just state.

Lock-free mutation of state using CAS (Compare-And-Swap) operations is a powerful technique for concurrent programming.

In my previous post, Immutable Beans, I explored similar ideas.

But how do you manage state without locks, especially if you want something as elegant as Clojure’s atoms?

Here, I want to show how the “atom” concept from Clojure can be implemented in Java and F# using AtomicReference and Interlocked, to provide lock-free transactional memory for a single value.

Most programs really only need one variable. Just one state, the state. That state can be complex: a mutable tree with many branches, maps, or whatever suits your data. But in the end, it’s all just mutable state.

Immutable state

When your entire state is represented by an immutable value— say, a tree structure or a nested map—you cannot change the data in place. Any “update” means creating a new version of the tree, typically reusing most of the data from its predecessor.

Updating state is simply a matter of changing the variable’s reference from the old tree to the new one. If you come from a functional programming background, this feels natural. For many Java developers, though, it’s a shift in perspective.

Whether your state is a record, an immutable object, or a persistent map, the key property is immutability.

Often, your state contains collections—maps, sets, vectors, lists. In Java, persistent (i.e., immutable and efficiently updatable) data structures aren’t built-in. That’s why I wrote my own:

Persistent Data strutctures in Java

With persistent data structures, every update creates a new collection, but under the hood, it shares most of its memory with the old version, a technique called structural sharing.

F#, as a functional language, does have persistent maps, sets, and lists by default. Linked lists are naturally immutable. But F# lacks a persistent random-access vector, so I built one:

Persistent Vector in F#

These are the tools you need to build real immutable state, and they’re what make lock-free concurrency practical.

Concurrency

Then there is the question how you update this variable. Do you aquire a lock to prevent concurrent updates, or do you allow for free competition. Since every update is a total new variable there is not much that can go wrong. Every reader of state fetches a state once, and uses that immutable, stable, version of the state for all its decision, until it is ready to fetch changes again. There are no sudden surprises. Writers can compete concurrently, but one will win when conflicts occur, while the rest will have to try the write once again, until free of conflict. Conflict resoultion can be advanced, while we demonstrate the most simple one here

Regardless of method, it is important to induce a memory barrier so that all participants, threads, understand that the memory of the state physically has to be read again. The compiler can’t optimize these variables away, memory cache has to be cleared and so on. Locks helps you with this, as it is not only used for mutual exclusion, and here we utilize lower level construct to induce lock-free synchronization of memory.

Also there has to be some protocol on how updates are made. It’s not good if one participant, a thread, works with a too old example of the state, and ignores what other participants decided recently. All updates of the state should be considered, otherwise wrong decisions will be made. This is super complex when there are many variables in a program, many variables that change out of control, regardless of wether you use locks or not. But it becomes simple when there are just one variable, only one thing to keep track of.

There are few common ways to do this in Java and also not in many functional languages like F#. In F# you probably roll your own state monad, or similar. In Clojure, another functional programming language we tend to use transactional memory. That is a system where the internal memory is updated as if it was in a transactional database. The update of a variable only succeedes if the conditions are right. Most clojure programs do that with the single state variable, while everything else is fully immutable.

I rant a bit. There many ways to manage state gracefully in F#, and Java. But doing it as simple as often done in Clojure, with simple use of the CAS operation is not common. But we do have AtomicReference in Java, and Interlocked in .NET.

The key question is: How do you safely update your single, immutable state variable when multiple threads want to write?

One approach is to acquire a lock before every update, preventing concurrent changes. But things get easier with immutability. Every update produces a brand new state, so readers always see a consistent snapshot—without the risk of surprise mutations happening behind your back.

When several writers compete, only one should succeed. Only one should swap in its version, while the others have to try again with the new value. This pattern, known as lock-free concurrency, is enabled by atomic operations like CAS (Compare-And-Swap).

It’s important, though, to ensure that all threads see the latest value. This is where memory barriers come in: they make sure changes to the state variable are visible to all threads. Locks do this for you automatically—not just introducing mutual exclusion, but also ensuring visibility via memory barriers. Atomic primitives like AtomicReference or Interlocked provide the same guarantees, without explicit locking.

Programs with many mutable variables quickly become messy as concurrency is introduced. Threads can easily make decisions based on outdated data. But with just one state variable, things become much simpler: every update is atomic, and everyone is working with a clear, immutable version of the state.

Not all languages make this easy. In Java and F#, you have to build some of these abstractions yourself. But the tools are there: AtomicReference in Java, Interlocked in .NET.

The result is a concurrency model that’s robust, simple, and easy to reason about—especially when your state is immutable and centralized.

Clojure atoms

A very simple example of state with an atom, transactional memory of one, in Clojure:


;; An atom holds a reference to your application state, a map with an int and a vector.
(def state (atom {:value 23
                  :numbers [1 2 3]}))

;; Updates are done with pure functions that return a new value. There is no mutation.
(defn increase-number [current-state]
  ;; This creates a new map with :value incremented.
  (update current-state :value inc))

;; swap! applies the function atomically.
;; If another thread updates the atom first, swap! retries with the new value.
(swap! state increase-number)

;; To read the current value, use deref. You always get a stable, immutable snapshot.
(deref state) ; => {:value 24 :numbers [1 2 3]}

;; state with :value 24 increased from 23

Atom in Java

Let’s build the same kind of lock-free, thread-safe state as Clojure’s atom.

Java has AtomicReference. It is an abstraction around the CAS and similar low level instructions. Implementing the atom is fairly simple:

public class Atom<T> {
    private final AtomicReference<T> ref;

    // Create an atom with a value
    public Atom(T initialValue) {
        this.ref = new AtomicReference<>(initialValue);
    }

    // Get the value of the atom. If the value is immutable you can relly on its stability. 
    // The get method imposes a barrier, that makes sure you can read what the previous 
    // thread knew, when it wrote to the same atomic reference.
    
    public T deref() {
        return ref.get();
    }

    // Apply the function on the value of the atom, repeatedly until the value of 
    // the atom remain the same after the function returns it's new value.
    // CompareAndSet does also impose read and write barrier, making sure the knowedge of 
    // the previous thread writing to the atomic reference is visible. 
    
    public T swap(Function<T, T> f) {
        while (true) {
            T oldValue = ref.get();
            T newValue = f.apply(oldValue);
            if (ref.compareAndSet(oldValue, newValue)) {
                return newValue;
            }
        }
    }

This simple implementation of swap is already implemented in AtomicReference.updateAndGet. Here we show how it’s implemented, but we will elaborate on this further down. It’s important to understand that compareAndSet imposes a memory barrier. Data written before compareAndSet by one thread is visible afterwards by another thread after reading the value of the same AtomicReference.

So lets look at a small example.

java example


  //We create a class to represent our state, just like the Clojur variant, 
  //a integer value and a list of integers, or here an immutable Iterable. 
 class State{
    final int value;
    final Iterable<Integer> numbers;
    State (int value, Iterable<Integer> numbers){
        this.value = value;
        this.numbers = numbers;
    }

    @Override
    public String toString() {
        return "value:" + value + ", numbers:" +  asList(numbers);
    }
}

// A function to translate that iterable to a list, using the streams, 
// like needed in the toString method
static <T> List<T> asList(Iterable<T> i){
    return StreamSupport.stream(i.spliterator(), false)
            .collect(Collectors.toList());
}

// A simple function to create state, to simplify syntax
static State state(int value, Iterable<Integer> numbers){
    return new State(value, numbers);
}

// Another function to create an atom, also to simplify syntax
static <T> Atom<T> atom(T t){
    return new Atom<>(t);
}

// The ridiculously simple domain function, that increas value in the 
// immutable state, by creating a new state value 
static State increaseValue (State state){
    return state(state.value + 1, state.numbers);
}

// And then a few expressions to create the atom 
Atom<State> state = atom(state(23, Arrays.asList(1, 2, 3)));

// Perform some simplified bnusiness logic with thin a transaction
// Some logic that will be repeated by the atom, if concurrency occur.
state.swap(x -> increaseValue(x));

// Followed by retrieving a state value, that can be reasoned about in calmness
// and perhaps printed
System.out.println(state.deref());

Java with lock

Another alternative would be the following scenario where we use a lock, which prevents concurrency. Two threads with the same lock will not enter increaseValue simultaneously.

// Create a lock object, for concurrent threads to acquire  
static Object lock = new Object();

public static void main(String... args){
    State state = state(23, Arrays.asList(1, 2, 3));

    //Locking the lock object before increasing the value into a new state, without concurrency
    synchronized (lock) {
        state = increaseValue(state);
    }
    // and print the state
    System.out.println(state);
}

A scenario where concurrency can’t occur since we guard the business logic, that here is heavily simplified.

Java classic

A more classic Java style would probably be with an mutable state, which is very difficult to get right, that appear to almost always break state as thigs start to happen when you dont expect it to. Commonly you got a reference, through correct publishing, but that suddenly mutate under your feet when you least expect it to.

class MutableState{
    int value;
    List<Integer> numbers = new ArrayList<Integer>();
    MutableState( int value, List<Integer> numbers){
        this.value = value;
        this.numbers = numbers;
    }
    public synchronized void increaseValue(){
        value=value+1;
    }
    @Override
    public String toString() {
        return "value:" + value + ", numbers:" + numbers;

    }
}


MutableState state = new MutableState(23, Arrays.asList(1,2,3));
// where business logic does it with lock of state
state.increaseValue();

// And where the state can be updated by anyone, at any time, even 
// while printing the state, making you see unrelated values. 
System.out.println(state);

FSharp

Although F# is a functional language, it’s actually quite weak when it comes to reference types—that is, types you can use to hold mutable, thread-safe state. F# includes the MailboxProcessor, which is a very simple actor or agent model, but there’s nothing built-in that resembles transactional memory (like Clojure’s atom).

On the .NET platform, however, we do have the Interlocked class, which exposes low-level CAS (Compare-And-Swap) operations. This makes it possible to implement an atom abstraction. Interlocked.CompareExchange works much like AtomicReference.compareAndSet in Java.

There’s a catch, though. CompareExchange is declared as:

public static T CompareExchange<T>(ref T location, T value, T comparand);

This means you have to manage some low-level details yourself. The first argument, location, is a reference passed by ref. While F# records are typically very nice to work with, their internal optimizations can make them incompatible with these low-level constructs—you’ll get compiler errors if you try to take the address of a value captured by a closure.

As a result, it’s usually easier to use traditional reference types, like ordinary classes, for atom-like constructs in F#.

.NET supports not only reference types, but also value types (structs in F#). But CompareExchange only works for reference types (unless you use a specialized overload for primitives). To support both records and value types, we can use typed boxing—wrapping the value in a class—so everything is stored as a reference.


// A typed wrapper, box, so we can use reference equality for CAS operations.
// Each Cell<'T> wraps an immutable value, but is itself a reference type.

type Cell<'T>(v: 'T) =
    member _.Value = v

// The Atom type holds a mutable reference to a Cell<'T>.
// It's just a regular .NET class.

type Atom<'T> =
    val mutable  cell: Cell<'T>
    new (initial: 'T) =
        { cell = Cell initial }
    
// Helper to create an atom.

let atom (initial: 'T) : Atom<'T> = 
    Atom(initial)

// Atomically applies function 'f' to the atom's value until the CAS succeeds.

let rec swap (a: Atom<'T>) (f: 'T -> 'T) : 'T =
    // Read latest cell
    let oldCell = Volatile.Read(&a.cell)
    // Extract value
    let oldVal = oldCell.Value
    // Apply function to value
    let newVal = f oldVal
    // Leave the atom as is if f didn't change value
    if newVal = oldVal then oldVal 
    else
        // Otherwise, box in new cell
        let newCell = Cell newVal
        // Only replace cell if still pointing to oldCell
        if Interlocked.CompareExchange(&a.cell, newCell, oldCell) = oldCell then
            newVal
        // or recur, to apply the function to the new concurrently updated value  
        else swap a f

// Read the value of the Atom, the immutable cell value
let deref (a: Atom<'T>) : 'T =
    Volatile.Read(&a.cell).Value

You might wonder whether you need a write barrier when constructing the atom. And you do. But it’s not enough to use Volatile.Write on the value inside the atom function, since the entire Atom object must be properly published to other threads. Unlike Java, .NET object creation does not guarantee a memory barrier. So if you pass a freshly created object to another thread, you must ensure visibility using appropriate synchronization.

Since FSharp is a functional language it is easy to use the Atom. Most types a are immutable, like records

// A record describing the state
type State = {
    Value : int
    Numbers : int list
}

//Some really simple application logic
let increaseValue (state :State) =
    { state with Value = state.Value + 1 }


let state = atom { Value = 23; Numbers = [1;2;3] }
swap state increaseValue |> ignore<State>
//Should with Value = 24
printf "%A" (deref state)

As you can see swap returns the state swapped in, we explicitly ignore it here. The same is true for both swap! in Clojure and our swap in Java. Dereferencing could give you the state from some concurrent usage, while the one returned is the one swapped in.

This raises an interesting question. Which states are we missing? Did someone else change program state in between our swap and deref? Obviously not in this simple example since there is no concurrent usage, but in a real world example, when threads are competing?

Watchers

Clojure atoms support “watchers”—callbacks that let you subscribe to state changes. This makes it easy to react to changes, or to build your own transaction log.


(def state (atom {:value 23 
                  :numbers [1 2 3]}))
                  
;; Add a watcher, identified as :my-watch that print the state transition

(add-watch state 
           :my-watch
           (fn [key _ from to]
              (println key from to)))

(defn increase-number [immutable-state]
  (update immutable-state :value inc))
  
(println (swap! state increase-number))

;;Since both the watcher and the returned value is printed, you will likely se:
;; :my-watch {:value 23 :numbers [1 2 3]} {:value 24 :numbers [1 2 3]}
;; {:value 24 :numbers [1 2 3]}

With watchers, you can log all state transitions, trigger side effects, or even keep a separate audit trail—without cluttering your business logic.

watchers in F#

It is pretty simple to add this to both F# and Java.

module Atom 
open System
open System.Threading
      
type Cell<'T>(v: 'T) =
    member _.Value = v

type Atom<'T> =
    val mutable  cell: Cell<'T>
    // Add a member for a watch function, with old and new value
    val listener: Option<'T -> 'T -> unit>
    new (initial: 'T) =
        { cell = Cell initial
          listener = None }
    //Another constructor, for initial and the watch callback
    new (initial : 'T, callback : 'T -> 'T -> unit) =
        { cell = Cell initial
          listener = Some callback}

let atom (initial: 'T) : Atom<'T> = 
    Atom(initial)

// A simple way 
let atomWithListener (initial: 'T) (listener : 'T -> 'T -> unit) =
    Atom(initial, listener)

let deref (a: Atom<'T>) : 'T =
    Volatile.Read(&a.cell).Value

let rec swap (a: Atom<'T>) (f: 'T -> 'T) : 'T =
    let oldCell = Volatile.Read(&a.cell)
    let oldVal = oldCell.Value
    let newVal = f oldVal
    if newVal = oldVal then oldVal 
    else
        let newCell = Cell newVal
        if Interlocked.CompareExchange(&a.cell, newCell, oldCell) = oldCell then
            // Update any listener when value changes 
            match a.listener with
            | Some cb -> cb oldVal newVal
            | None -> ()
            newVal
        else swap a f

// And lets add reset, that changes the value of the atom unconditionally
// but participates in safe publishing and listener notification
let reset (a: Atom<'T>) (value: 'T) : unit =
    let oldCell = Interlocked.Exchange(&a.cell, Cell value)
    match a.listener with
    | Some cb -> cb oldCell.Value value
    | None -> ()

// And lets add a more classic compare and swap of the atom, or in .NET 
// jargon CompareExchange of a value of atom, whith notification to watcher  
let compareAndSwap (a: Atom<'T>) (expected: 'T) (next: 'T) : bool =
    let oldCell = Volatile.Read(&a.cell)
    let oldVal = oldCell.Value
    if oldVal <> expected then
        false
    else
        let newCell = Cell next
        if Interlocked.CompareExchange(&a.cell, newCell, oldCell) = oldCell then
            match a.listener with
            | Some cb -> cb oldVal next
            | None -> ()
            true
        else false

And it’s used similarly:


type State = {
    Value : int
    Numbers : int list
}
    
let increaseValue (state :State) =
    { state with Value = state.Value + 1 }

[<EntryPoint>]
let main args : int =
    let state = atomWithListener 
                  { Value = 23; 
                    Numbers = [1;2;3] } 
                  (fun from until -> printfn "from %A to: %A" from until )
  
    swap state increaseValue |> ignore<State>
    printfn "%A" (deref state)

    reset state {Value = 1; Numbers = [4]}
    printfn "%A" (deref state)
    0

//Should print:

from { Value = 23 ; Numbers = [1; 2; 3] } to: { Value = 24 ; Numbers = [1; 2; 3] }
{ Value = 24 Numbers = [1; 2; 3] }

Java with watchers

Java actually makes it straightforward to add watcher (listener) support, thanks to its rich concurrency libraries.

AtomicReference handles lock-free updates, and CopyOnWriteArrayList is perfect for storing listeners: This list copies the underlaying array on mutation, guarded by a lock, and refers the data as a volatile array. For small sets of listeners, this is an efficient, thread-safe approach.

package stonehorse.candy;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.BiConsumer;

// An Atom abstraction, inspired by Clojure's atom with watchers/listeners.
public class Atom<T> {
    private final AtomicReference<T> ref;
    private final CopyOnWriteArrayList<BiConsumer<T, T>> listeners = new CopyOnWriteArrayList<>();

    public Atom(T initialValue) {
        this.ref = new AtomicReference<>(initialValue);
    }

    // Get the current (immutable) value.
    public T deref() {
        return ref.get();
    }
    
    // Atomically update the value with a pure function, notify listeners if changed.
    public T swap(Function<T, T> f) {
        while (true) {
            T oldValue = ref.get();
            T newValue = f.apply(oldValue);
            if (ref.compareAndSet(oldValue, newValue)) {
                notifyListeners(oldValue, newValue);
                return newValue;
            }
        }
    }

    // Classic compare-and-swap, with notification.
    public boolean compareAndSwap(T expected, T next) {
        boolean updated = ref.compareAndSet(expected, next);
        if (updated) {
            notifyListeners(expected, next);
        }
        return updated;
    }

    // Set a new value unconditionally, notify listeners.
    public void reset(T value) {
        T oldValue = ref.getAndSet(value);
        notifyListeners(oldValue, value);
    }

    // Add or remove listeners (called with old and new values).
    public void addListener(BiConsumer<T, T> listener) {
        listeners.add(listener);
    }

    public void removeListener(BiConsumer<T, T> listener) {
        listeners.remove(listener);
    }

    private void notifyListeners(T oldValue, T newValue) {
        for (var listener : listeners) {
            listener.accept(oldValue, newValue);
        }
    }
}

And an example:

Atom<Integer> state = new Atom<>(23);

state.addListener((from, to) ->
    System.out.println("State changed from " + from + " to " + to)
);

state.swap(x -> x + 1);
// Output: State changed from 23 to 24

state.reset(42);
// Output: State changed from 24 to 42

Summary

Most programs can manage just fine with a single variable for all application state— even if that variable is complex and contains your entire data model.

By using immutable state and atomic operations like Compare-And-Swap (CAS), you can update this state variable in a thread-safe, lock-free way—eliminating race conditions and unpredictable bugs.

Clojure’s atom demonstrates how simple this can be: you swap out the entire state variable atomically, with no need for locks.

Java achieves the same pattern with AtomicReference and immutable objects; it’s also easy to add “watchers” (listeners) for reactive flows or audit logging.

F# can do it too, using Interlocked.CompareExchange and immutable records—again, all updates happening atomically on a single variable.

Atom.fs

Concurrency becomes robust and easy to reason about, even with many threads.


All #art #clojure #csharp #data-structures #database #datomic #emacs #fsharp #functional #haskell #history #immutability #java #jit #jmm #lambdas #lisp #pioneers #poetry #programming #programming-philosophy #randomness #rant #reducers #repl #smalltalk #sql #threads #women