什么时候在 Java 中使用 AtomicReference?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3964211/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 07:41:37  来源:igfitidea点击:

When to use AtomicReference in Java?

javamultithreading

提问by Chintu

When do we use AtomicReference?

我们什么时候使用AtomicReference

Is it needed to create objects in all multithreaded programs?

是否需要在所有多线程程序中创建对象?

Provide a simple example where AtomicReference should be used.

提供一个应该使用 AtomicReference 的简单示例。

采纳答案by andersoj

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 check to see if a specific field only if the state of the object remains as you last checked:

原子引用应该用于需要对引用执行简单原子(即线程安全、非平凡)操作的设置,对于这种情况,基于监视器的同步不合适。假设您要检查特定字段是否仅当对象的状态保持为您上次检查时:

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,cachedValueToUpdate);

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

由于原子引用语义,即使cache对象在线程之间共享,您也可以这样做,而无需使用synchronized. 一般来说,除非您知道自己在做什么,否则最好使用同步器或java.util.concurrent框架而不是裸Atomic*

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

两个优秀的死树参考将向您介绍这个主题:

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

请注意(我不知道这是否一直如此)引用赋值(即=)本身是原子的(更新原始64 位类型,例如longdouble可能不是原子的;但更新引用始终是原子的,即使它是 64 位) 而不显式使用Atomic*.
请参阅Java 语言规范 3ed,第 17.7 节

回答by Erik Helleren

An atomic reference is ideal to use when you need to share and change the state of an immutable object between multiple 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 of Long and Double, as well as String, just to name a few. (According to Programming Concurrency on the JVMimmutable objects are a critical part of modern concurrency).

首先,不可变对象是在构造后实际上不会改变的对象。不可变对象的方法经常返回同一个类的新实例。一些示例包括 Long 和 Double 的包装类,以及 String,仅举几例。(根据JVM 上的编程并发,不可变对象是现代并发的关键部分)。

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

接下来,为什么 AtomicReference 在共享共享值方面比 volatile 对象更好。一个简单的代码示例将显示差异。

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.

每次您想根据当前值修改该 volatile 字段引用的字符串时,您首先需要获得对该对象的锁定。这可以防止其他线程在此期间进入并更改新字符串连接中间的值。然后当你的线程恢复时,你破坏了另一个线程的工作。但老实说,代码会起作用,它看起来很干净,而且会让大多数人感到高兴。

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.

轻微问题。它很慢。特别是如果那个锁对象有很多争用。那是因为大多数锁需要 OS 系统调用,并且您的线程将被阻塞并被上下文切换出 CPU,以便为其他进程让路。

The other option is to use an AtomicRefrence.

另一种选择是使用 AtomicRefrence。

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()+"lets 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!

现在为什么这更好?老实说,代码比以前少了一些。但是在 AtomicRefrence 的幕后发生了一些非常重要的事情,那就是比较和交换。使切换发生的是单个 CPU 指令,而不是操作系统调用。那是 CPU 上的一条指令。并且因为没有锁,所以在锁被执行的情况下没有上下文切换,这可以节省更多时间!

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.

问题是,对于 AtomicReferences,这不使用 .equals() 调用,而是使用 == 比较期望值。因此,请确保预期是循环中从 get 返回的实际对象。

回答by Binita Bharati

Here is a use case for AtomicReference:

这是 AtomicReference 的一个用例:

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

考虑这个充当数字范围的类,并使用单独的 AtmomicInteger 变量来维护数字下限和上限。

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.

setLower 和 setUpper 都是先检查后行动的序列,但它们没有使用足够的锁定来使它们具有原子性。如果数字范围为 (0, 10),并且一个线程调用 setLower(5) 而另一个线程调用 setUpper(4),那么由于一些不幸的时间,两个线程都将通过 setter 中的检查,并且将应用两个修改。结果是范围现在持有 (5, 4) 一个无效状态。因此,虽然底层 AtomicIntegers 是线程安全的,但复合类不是。这可以通过使用 AtomicReference 而不是使用单独的 AtomicIntegers 作为上限和下限来解决。

public class CasNumberRange {
    //Immutable
    private static class IntPair {
        final int lower;  // Invariant: lower <= upper
        final int upper;
        ...
    }
    private final AtomicReference<IntPair> values =
        new AtomicReference<IntPair>(new IntPair(0, 0));

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

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

回答by HamoriZ

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

您可以在应用乐观锁时使用 AtomicReference。您有一个共享对象,并且想要从 1 个以上的线程中更改它。

  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.
  1. 您可以创建共享对象的副本
  2. 修改共享对象
  3. 您需要检查共享对象是否仍与以前相同 - 如果是,则使用修改后的副本的引用进行更新。

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

由于其他线程可能已修改它和/可以在这两个步骤之间进行修改。您需要在原子操作中执行此操作。这是 AtomicReference 可以提供帮助的地方

回答by Dherik

Another simple example is to do a safe-thread modification in a session object.

另一个简单的例子是在会话对象中进行安全线程修改。

public PlayerScore getHighScore() {
    ServletContext ctx = getServletConfig().getServletContext();
    AtomicReference<PlayerScore> holder 
        = (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
    return holder.get();
}

public void updateHighScore(PlayerScore newScore) {
    ServletContext ctx = getServletConfig().getServletContext();
    AtomicReference<PlayerScore> holder 
        = (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
    while (true) {
        HighScore old = holder.get();
        if (old.score >= newScore.score)
            break;
        else if (holder.compareAndSet(old, newScore))
            break;
    } 
}

Source: http://www.ibm.com/developerworks/library/j-jtp09238/index.html

来源:http: //www.ibm.com/developerworks/library/j-jtp09238/index.html

回答by Ravindra babu

When do we use AtomicReference?

我们什么时候使用 AtomicReference?

AtomicReferenceis flexible way to update the variable value atomically without use of synchronization.

AtomicReference是一种灵活的方式,可以在不使用同步的情况下以原子方式更新变量值。

AtomicReferencesupport lock-free thread-safe programming on single variables.

AtomicReference支持对单个变量进行无锁线程安全编程。

There are multiple ways of achieving Thread safety with high level concurrentAPI. Atomic variables is one of the multiple options.

有多种方法可以通过高级并发API实现线程安全。原子变量是多个选项之一。

Lockobjects support locking idioms that simplify many concurrent applications.

Lock对象支持简化许多并发应用程序的锁定习惯用法。

Executorsdefine a high-level API for launching and managing threads. Executor implementations provided by java.util.concurrent provide thread pool management suitable for large-scale applications.

Executors定义用于启动和管理线程的高级 API。java.util.concurrent 提供的 Executor 实现提供了适合大型应用程序的线程池管理。

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

并发集合可以更轻松地管理大型数据集合,并且可以大大减少对同步的需求。

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

原子变量具有最小化同步并有助于避免内存一致性错误的功能。

Provide a simple example where AtomicReference should be used.

提供一个应该使用 AtomicReference 的简单示例。

Sample code with AtomicReference:

示例代码AtomicReference

String initialReference = "value 1";

AtomicReference<String> someRef =
    new AtomicReference<String>(initialReference);

String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);

Is it needed to create objects in all multithreaded programs?

是否需要在所有多线程程序中创建对象?

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

您不必AtomicReference在所有多线程程序中使用。

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

如果要保护单个变量,请使用AtomicReference. 如果要保护代码块,请使用其他结构,例如Lock/synchronized等。

回答by Benny Bottema

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

这是一个非常简单的用例,与线程安全无关。

To share an object between lambda invocations, the AtomicReferenceis an option:

要在 lambda 调用之间共享对象,这AtomicReference是一个选项

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 AtomicReferenceis an option.

我并不是说这是一个好的设计或任何东西(这只是一个简单的例子),但如果您有需要在 lambda 调用之间共享对象的情况,这AtomicReference是一个选项。

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.

事实上,您可以使用任何持有引用的对象,甚至是只有一项的 Collection。然而,AtomicReference 是一个完美的选择。

回答by sankar banerjee

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.

一些重要的基本事实如下。1> 不同的线程只能竞争堆空间中的实例和静态成员变量。2> 易失性读或写是完全原子的和序列化/发生在之前,并且只能从内存中完成。这么说我的意思是任何读取都将跟随内存中的先前写入。并且任何写入都将跟随上一次从内存中读取。因此,任何使用 volatile 的线程将始终看到最新的值。 AtomicReference 使用 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.

以下是 AtomicReference 的一些源代码。AtomicReference 指的是一个对象引用。该引用是 AtomicReference 实例中的一个可变成员变量,如下所示。

private volatile V value;

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

get() 只是返回变量的最新值(就像 volatiles 以“发生在之前”的方式那样)。

public final V get()

Following is the most important method of AtomicReference.

以下是 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.

compareAndSet(expect,update) 方法调用 Java 的不安全类的 compareAndSwapObject() 方法。unsafe 的这个方法调用调用了本地调用,它向处理器调用了一条指令。“expect”和“update”分别引用一个对象。

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.

当且仅当 AtomicReference 实例成员变量“value”指代同一个对象被“expect”引用时,现在将“update”分配给该实例变量,并返回“true”。否则,返回 false。整个事情是原子地完成的。没有其他线程可以在两者之间进行拦截。由于这是一个单处理器操作(现代计算机架构的魔法),它通常比使用同步块更快。但请记住,当多个变量需要以原子方式更新时,AtomicReference 将无济于事。

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.

我想添加一个完整的运行代码,可以在 eclipse 中运行。它会清除许多混乱。这里有 22 个用户(MyTh 线程)正在尝试预订 20 个座位。以下是代码片段,后跟完整代码。

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

22 个用户尝试预订 20 个座位的代码片段。

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
            }
        }
    }

}