When to use AtomicReference in Java?
Asked Answered
R

7

443

When do we use AtomicReference?

Is it needed to create objects in all multithreaded programs?

Provide a simple example where AtomicReference should be used.

Rollick answered 18/10, 2010 at 23:15 Comment(0)
L
291

Atomic reference should be used in a setting where you need to do simple atomic (i.e. thread-safe, non-trivial) operations on a reference, for which monitor-based synchronization is not appropriate. Suppose you want to set a specific field only if the state of the object has changed during processing:

AtomicReference<Object> cache = new AtomicReference<Object>();

Object cachedValue = new Object();
cache.set(cachedValue);

//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,newValue);

Because of the atomic reference semantics, you can do this even if the cache object is shared amongst threads, without using synchronized. In general, you're better off using synchronizers or the java.util.concurrent framework rather than bare Atomic* unless you know what you're doing.

Two excellent dead-tree references which will introduce you to this topic:

Note that (I don't know if this has always been true) reference assignment (i.e. =) is itself atomic (updating primitive 64-bit types like long or double may not be atomic; but updating a reference is always atomic, even if it's 64 bit) without explicitly using an Atomic*.
See the Java Language Specification 3ed, Section 17.7.

Leonardo answered 18/10, 2010 at 23:22 Comment(15)
Correct me if I'm wrong, but it seems like the key to needing this is because you need to do a "compareAndSet". If all I needed to do was set I wouldn't need the AtomicObject at all because of reference updates themselves being atomic?Applaud
Is it safe to do cache.compareAndSet(cachedValue, someFunctionOfOld(cachedValueToUpdate))? I.e. inline the computation?Hayrick
@veggen Function arguments in Java are evaluated before function itself, so inlining makes no difference in this case. Yes, it is safe.Knipe
@Applaud That is correct, but if you're not using AtomicReference you should mark the variable volatile because while the runtime guarantees that reference assignment is atomic, the compiler may perform optimizations under the assumption that the variable was not being modified by other threads.Michalmichalak
@Michalmichalak only if you change your AtomicReference field (cache in the code above) to a different AtomicReference. If you keep the same one, like in the answer, you shouldn't need volatileGuillaume
@BradCupit note that I said "if you're not using AtomicReference"; if you are using it, then my advice would be to go in the opposite direction and mark it final so the compiler can optimize accordingly.Michalmichalak
@Michalmichalak Whoops! You're right: not using AtomicReference it would be good to use volatile. And using final for an AtomicReference also sounds good.Guillaume
Shouldn't the last line be boolean success = cache.compareAndSet(cachedValueToUpdate, newValue); ?Murky
@Murky : No it should be just as it was written because compareAndSet: Atomically sets the value to newValue if the current value == expectedValue So it would return true so long as the cached value didn't change and it was set to the new value, if not it returns false and the value currently stored is not changed (at least that's how I understood it)Grin
This ton of text does not answer the main post question: when to use it? Words "use it when you know what you're doing" - are not the answer.Altocumulus
@Murky Yes I'm pretty sure you are right. The newValue is just thrown away. The value put into the cache is the one that came out of it, cachedValueToUpdate. Fascinating that it took ten years and 236 upvotes to find.Cranmer
@Grin your explanation itself is correct. But that's not what the example in the answer above is quite doing. What you refer to as expectedValue should be cachedValueToUpdate in the example above, not cachedValue.Murky
@Cranmer thanks, how can we get the post to get reviewed & updated?Murky
I strongly disagree with you're better off using synchronizers or the java.util.concurrent framework rather than bare Atomic* unless you know what you're doing. Even when it is appropriate - atomic package classes are (1) safer since they encapsulate thread-safety code; (2) more readable, since they encapsulate thread-safety code :) and explicitly declare what they safe-guard; and (3) lock-free hence faster! So, IMHO, it should be the other way around - monitor-based synchronization should be the last resort, in case the use of atomic package classes aren't appropriate.Levitan
Could anyone rephrase this: "Suppose you want to check to see if a specific field only if the state of the object remains as you last checked:" What do we want?Golden
N
138

An atomic reference is ideal to use when you need to update content (in an immutable object) accessed by multiple threads by replacing it with a new copy (of the immutable object) shared between these threads. That is a super dense statement, so I will break it down a bit.

First, an immutable object is an object that is effectively not changed after construction. Frequently, an immutable object's methods return new instances of that same class. Some examples include the wrapper classes Long and Double, as well as String, just to name a few. (According to Programming Concurrency on the JVM, immutable objects are a critical part of modern concurrency.)

Next, why is an AtomicReference better than a volatile object for sharing that shared value? A simple code example will show the difference.

volatile String sharedValue;

static final Object lock = new Object();

void modifyString() {
    synchronized (lock) {
        sharedValue = sharedValue + "something to add";
    }
}

Every time you want to modify the string referenced by that volatile field based on its current value, you first need to obtain a lock on that object. This prevents some other thread from coming in during the meantime and changing the value in the middle of the new string concatenation. Then when your thread resumes, you clobber the work of the other thread. But honestly that code will work, it looks clean, and it would make most people happy.

Slight problem. It is slow. Especially if there is a lot of contention of that lock Object. Thats because most locks require an OS system call, and your thread will block and be context switched out of the CPU to make way for other processes.

The other option is to use an AtomicReference.

public static AtomicReference<String> shared = new AtomicReference<>();
String init = "Inital Value";
shared.set(init);
//now we will modify that value
boolean success = false;
while (!success) {
    String prevValue = shared.get();
    // do all the work you need to
    String newValue = shared.get() + "let's add something";
    // Compare and set
    success = shared.compareAndSet(prevValue, newValue);
}

Now why is this better? Honestly that code is a little less clean than before. But there is something really important that happens under the hood in AtomicRefrence, and that is compare and swap. It is a single CPU instruction, not an OS call, that makes the switch happen. That is a single instruction on the CPU. And because there are no locks, there is no context switch in the case where the lock gets exercised which saves even more time!

The catch is, for AtomicReferences, this does not use a .equals() call, but instead an == comparison for the expected value. So make sure the expected is the actual object returned from get in the loop.

Nils answered 15/7, 2014 at 18:15 Comment(11)
Your two examples behave differently. You'd have to loop on worked to get the same semantics.Felix
I think you should initialize the value inside the AtomicReference constructor, otherwise another thread may still see the value null before you call shared.set. (Unless shared.set is run in a static initializer.)Persiflage
In your second example, you should as of Java 8 use something like: shared.updateAndGet( (x) -> (x+"lets add something")); ... which will repeatedly call the .compareAndSet until it works. That is equivalent to the synchronized block which would always succeed. You need to ensure that the lambda you pass in is side-effect-free though because it may be called multiple times.Sillimanite
@Felix Loop to hope that shared might become prevValue again? Good luck :)Cyanotype
No, typically you check the value, perform your work and then do the compare and swap all within the loop. It's a very common pattern in lock free algorithms, but I have seen it perform abysmally in certain non-obvious scenarios so TBH I tend to prefer locking solutions (if you can't avoid sharing state of course).Felix
There is not need to make volatile String sharedValue. The synchronized(lock) is good enough to establish the happen before the relationship.Phillada
"...change the state of an immutable object" is imprecise here, in part because being literal you can't change the state of an immutable object. The example demonstrates changing the reference from one immutable object instance to a different one. I realize that's pedantic but I think it's worth highlighting given how confusing Thread logic can be.Encouragement
"change the state of an immutable object " - that's sounds like oksymoron. You cannot change state of immutable object, by definition. You can only change a reference to an immutable object.Meshed
Excellent Answer; thank you. However, I agree that first sentence could be improved. Perhaps change "… when you need to share and change the state of an immutable object between multiple threads." to "…when you need to update content by replacing an immutable object shared between threads."Anaconda
" change the state of an immutable object " - you're kidding, right?Altocumulus
Regarding the comments on the immutable object sentence put forth above: "an immutable object is an object that is effectively not changed after construction": is the person referring to objects which are not immutable in the strict sense but once constructed and assigned to the AtomicReference their field values are not changed - so the object is 'effectively immutable' in a narrow way. If the objects state (but not the reference stored in the AtomicRef) were to change, then the update might not be visible to other threadsKnockwurst
N
45

Here is a use case for AtomicReference:

Consider this class that acts as a number range, and uses individual AtmomicInteger variables to maintain lower and upper number bounds.

public class NumberRange {
    // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

    public void setLower(int i) {
        // Warning -- unsafe check-then-act
        if (i > upper.get())
            throw new IllegalArgumentException(
                    "can't set lower to " + i + " > upper");
        lower.set(i);
    }

    public void setUpper(int i) {
        // Warning -- unsafe check-then-act
        if (i < lower.get())
            throw new IllegalArgumentException(
                    "can't set upper to " + i + " < lower");
        upper.set(i);
    }

    public boolean isInRange(int i) {
        return (i >= lower.get() && i <= upper.get());
    }
}

Both setLower and setUpper are check-then-act sequences, but they do not use sufficient locking to make them atomic. If the number range holds (0, 10), and one thread calls setLower(5) while another thread calls setUpper(4), with some unlucky timing both will pass the checks in the setters and both modifications will be applied. The result is that the range now holds (5, 4)an invalid state. So while the underlying AtomicIntegers are thread-safe, the composite class is not. This can be fixed by using a AtomicReference instead of using individual AtomicIntegers for upper and lower bounds.

public class CasNumberRange {
    // Immutable
    private static class IntPair {
        final int lower;  // Invariant: lower <= upper
        final int upper;

        private IntPair(int lower, int upper) {
            this.lower = lower;
            this.upper = upper;
        }
    }

    private final AtomicReference<IntPair> values = 
            new AtomicReference<IntPair>(new IntPair(0, 0));

    public int getLower() {
        return values.get().lower;
    }

    public void setLower(int lower) {
        while (true) {
            IntPair oldv = values.get();
            if (lower > oldv.upper)
                throw new IllegalArgumentException(
                    "Can't set lower to " + lower + " > upper");
            IntPair newv = new IntPair(lower, oldv.upper);
            if (values.compareAndSet(oldv, newv))
                return;
        }
    }

    public int getUpper() {
        return values.get().upper;
    }

    public void setUpper(int upper) {
        while (true) {
            IntPair oldv = values.get();
            if (upper < oldv.lower)
                throw new IllegalArgumentException(
                    "Can't set upper to " + upper + " < lower");
            IntPair newv = new IntPair(oldv.lower, upper);
            if (values.compareAndSet(oldv, newv))
                return;
        }
    }
}
Naarah answered 29/5, 2015 at 10:0 Comment(4)
This article is similar to your answer, but goes deep to more complicated things. It's interesting! ibm.com/developerworks/java/library/j-jtp04186Newfoundland
Hi! This link is broken, can you still find the available link to this article? @NewfoundlandChristenechristening
@Christenechristening found a cached URL - web.archive.org/web/20201109033000/http://www.ibm.com/…Cog
Is while(true) {} always required when invoking compareAndSet()? Is there a method available that loops automatically?Cog
B
31

You can use AtomicReference when applying optimistic locks. You have a shared object and you want to change it from more than 1 thread.

  1. You can create a copy of the shared object
  2. Modify the shared object
  3. You need to check that the shared object is still the same as before - if yes, then update with the reference of the modified copy.

As other thread might have modified it and/can modify between these 2 steps. You need to do it in an atomic operation. this is where AtomicReference can help

Baldwin answered 12/6, 2015 at 12:30 Comment(1)
Optimistic locks is the concept that I was looking for.Overtax
D
18

Here's a very simple use case and has nothing to do with thread safety.

To share an object between lambda invocations, the AtomicReference is an option:

public void doSomethingUsingLambdas() {

    AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();

    soSomethingThatTakesALambda(() -> {
        yourObjectRef.set(youObject);
    });

    soSomethingElseThatTakesALambda(() -> {
        YourObject yourObject = yourObjectRef.get();
    });
}

I'm not saying this is good design or anything (it's just a trivial example), but if you have have the case where you need to share an object between lambda invocations, the AtomicReference is an option.

In fact you can use any object that holds a reference, even a Collection that has only one item. However, the AtomicReference is a perfect fit.

Deuteranope answered 23/4, 2018 at 9:0 Comment(0)
I
15

When do we use AtomicReference?

AtomicReference is flexible way to update the variable value atomically without use of synchronization. It supports lock-free thread-safe programming on single variables.

There are multiple ways of achieving Thread safety concurrent API. Atomic variables is one of them.

Lock objects support locking idioms that simplify many concurrent applications.

Executors define a high-level API for launching and managing threads.

Concurrent collections make it easier to manage large collections of data, and can greatly reduce the need for synchronization.

Atomic variables have features that minimize synchronization and help avoid memory consistency errors.

Provide a simple example where AtomicReference should be used.

Sample code with AtomicReference:

String name1 = "Ravindra";

AtomicReference<String> reference =
    new AtomicReference<String>(name1 );

String name2 = "Ravindra Babu";
boolean result = reference.compareAndSet(name1 , name2 );
System.out.println("compareAndSet result: " + result );

Is it needed to create objects in all multithreaded programs?

You don't have to use AtomicReference in all multi threaded programs.

If you want to guard a single variable, use AtomicReference. If you want to guard a code block, use other constructs like Lock /synchronized etc.

Source: docs.oracle.com

Iveson answered 20/5, 2016 at 6:5 Comment(0)
F
6

I won't talk much. Already my respected fellow friends have given their valuable input. The full fledged running code at the last of this blog should remove any confusion. It's about a movie seat booking small program in multi-threaded scenario.

Some important elementary facts are as follows. 1> Different threads can only contend for instance and static member variables in the heap space. 2> Volatile read or write are completely atomic and serialized/happens before and only done from memory. By saying this I mean that any read will follow the previous write in memory. And any write will follow the previous read from memory. So any thread working with a volatile will always see the most up-to-date value. AtomicReference uses this property of volatile.

Following are some of the source code of AtomicReference. AtomicReference refers to an object reference. This reference is a volatile member variable in the AtomicReference instance as below.

private volatile V value;

get() simply returns the latest value of the variable (as volatiles do in a "happens before" manner).

public final V get()

Following is the most important method of AtomicReference.

public final boolean  compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

The compareAndSet(expect,update) method calls the compareAndSwapObject() method of the unsafe class of Java. This method call of unsafe invokes the native call, which invokes a single instruction to the processor. "expect" and "update" each reference an object.

If and only if the AtomicReference instance member variable "value" refers to the same object is referred to by "expect", "update" is assigned to this instance variable now, and "true" is returned. Or else, false is returned. The whole thing is done atomically. No other thread can intercept in between. As this is a single processor operation (magic of modern computer architecture), it's often faster than using a synchronized block. But remember that when multiple variables need to be updated atomically, AtomicReference won't help.

I would like to add a full fledged running code, which can be run in eclipse. It would clear many confusion. Here 22 users (MyTh threads) are trying to book 20 seats. Following is the code snippet followed by the full code.

Code snippet where 22 users are trying to book 20 seats.

for (int i = 0; i < 20; i++) {// 20 seats
    seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
    ths[i] = new MyTh(seats, i);
    ths[i].start();
}

Following is the full running code.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class Solution {

    static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
                                                // list index

    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        seats = new ArrayList<>();
        for (int i = 0; i < 20; i++) {// 20 seats
            seats.add(new AtomicReference<Integer>());
        }
        Thread[] ths = new Thread[22];// 22 users
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new MyTh(seats, i);
            ths[i].start();
        }
        for (Thread t : ths) {
            t.join();
        }
        for (AtomicReference<Integer> seat : seats) {
            System.out.print(" " + seat.get());
        }
    }

    /**
     * id is the id of the user
     * 
     * @author sankbane
     *
     */
    static class MyTh extends Thread {// each thread is a user
        static AtomicInteger full = new AtomicInteger(0);
        List<AtomicReference<Integer>> l;//seats
        int id;//id of the users
        int seats;

        public MyTh(List<AtomicReference<Integer>> list, int userId) {
            l = list;
            this.id = userId;
            seats = list.size();
        }

        @Override
        public void run() {
            boolean reserved = false;
            try {
                while (!reserved && full.get() < seats) {
                    Thread.sleep(50);
                    int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
                                                                            // seats
                                                                            //
                    AtomicReference<Integer> el = l.get(r);
                    reserved = el.compareAndSet(null, id);// null means no user
                                                            // has reserved this
                                                            // seat
                    if (reserved)
                        full.getAndIncrement();
                }
                if (!reserved && full.get() == seats)
                    System.out.println("user " + id + " did not get a seat");
            } catch (InterruptedException ie) {
                // log it
            }
        }
    }

}    
Frizzle answered 31/5, 2018 at 5:54 Comment(2)
you wrote "the most important method of AtomicReference is compareAndSet". what about the "set()" method ? it's atomic as well right ? if so, why not using set?Lui
compareAndSet first checks whether the value is null. Only then id is set. Otherwise not. set does not do anything like that.Frizzle

© 2022 - 2024 — McMap. All rights reserved.