Close

Java - AtomicStampedReference Example

[Updated: May 25, 2018, Created: May 25, 2018]

AtomicStampedReference<V> can be used to update a reference object and an int (stamp) atomically together.

Example

Let's see a scenario where we can use AtomicStampedReference.

The example initializes a piece of data in multiple steps, updating an int (state) on each step. First we are not going to use AtomicStampedReference.

package com.logicbig.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Supplier;

public class NoAtomicStampedRefExample {

    private static class Test {
        private static final int STATE_UNINITIALIZED = -1;
        private static final int STATE_INITIALIZING_IN_PROGRESS = 0;
        private static final int STATE_INITIALIZED = 1;
        private Supplier<String> dataService = getDataService();
        private int state = STATE_UNINITIALIZED;
        private String data;

        public void init() {
            if (state == STATE_UNINITIALIZED) {
                state = STATE_INITIALIZING_IN_PROGRESS;
                data = dataService.get();
                state = STATE_INITIALIZED;
            }
        }

        private static Supplier<String> getDataService() {
            return () -> {
                //sleep a little to simulate the service
                LockSupport.parkNanos(1000);
                return "test string";
            };
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 50; i++) {//repeat process multiple times
            Test test = new Test();
            ExecutorService es = Executors.newFixedThreadPool(2);

            //reader thread
            es.execute(() -> {
                while (true) {
                    int state = test.state;
                    boolean dataNotNull = test.data != null;
                    //print unexpected
                    if (state != Test.STATE_INITIALIZED && dataNotNull) {
                        System.out.printf("state: %s, data: %s%n", state, test.data);
                    }
                    //break on expected initialization
                    if (state == Test.STATE_INITIALIZED && dataNotNull) {
                        break;
                    }
                    LockSupport.parkNanos(1);
                }
            });
            //writer thread
            es.execute(test::init);

            es.shutdown();
            es.awaitTermination(10, TimeUnit.MINUTES);
        }
        System.out.println("finished");
    }
}
state: 0, data: test string
state: 0, data: test string
state: 0, data: test string
state: 0, data: test string
finished

As seen in above output, the reader thread can see an invalid state when data has been initialized but state is not updated to 'initialized' yet.

Using AtomicStampedReference

package com.logicbig.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Supplier;

public class AtomicStampedRefExample {

    private static class Test {
        private static final int STATE_UNINITIALIZED = -1;
        private static final int STATE_INITIALIZING_IN_PROGRESS = 2;
        private static final int STATE_INITIALIZED = 1;
        private Supplier<String> dataService = getDataService();
        private AtomicStampedReference<String> ref = new AtomicStampedReference<>(null, STATE_UNINITIALIZED);

        public void init() {
            if (ref.compareAndSet(null, null, STATE_UNINITIALIZED, STATE_INITIALIZING_IN_PROGRESS)) {
                String data = dataService.get();
                ref.compareAndSet(null, data, STATE_INITIALIZING_IN_PROGRESS, STATE_INITIALIZED);
            }
        }

        private static Supplier<String> getDataService() {
            return () -> {
                //sleep a little to simulate the service
                LockSupport.parkNanos(1000);
                return "test string";
            };
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {//repeat process multiple times
            Test test = new Test();
            ExecutorService es = Executors.newFixedThreadPool(2);

            //reader thread
            es.execute(() -> {
                while (true) {
                    int[] state = {-1};
                    String data = test.ref.get(state);
                    boolean dataNotNull = data != null;
                    //print unexpected
                    if (state[0] != Test.STATE_INITIALIZED && dataNotNull) {
                        System.out.printf("state: %data, data: %data%n", state, data);
                    }
                    //break on expected initialization
                    if (state[0] == Test.STATE_INITIALIZED && dataNotNull) {
                        break;
                    }
                    LockSupport.parkNanos(1);
                }
            });

            //writer thread
            es.execute(test::init);

            es.shutdown();
            es.awaitTermination(10, TimeUnit.MINUTES);
        }
        System.out.println("finished");
    }
}
finished

AtomicStampedReference can be used for various purposes. From Java Api doc:

The AtomicStampedReference class associates an integer value with a reference. This may be used for example, to represent version numbers corresponding to series of updates.

Example Project

Dependencies and Technologies Used:

  • JDK 10
  • Maven 3.3.9

AtomicStampedReference Example Select All Download
  • atomic-stamped-reference-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • AtomicStampedRefExample.java

    See Also