380

我们什么时候使用AtomicReference

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

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

4

8 回答 8

254

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

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

由于原子引用语义,即使cache对象在线程之间共享,您也可以执行此操作,而无需使用synchronized. 通常,除非您知道自己在做什么,否则最好使用同步器或java.util.concurrent框架而不是裸机。Atomic*

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

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

于 2010-10-18T23:22:48.117 回答
114

当您需要在多个线程之间共享和更改不可变对象的状态时,原子引用是理想的选择。这是一个超级密集的陈述,所以我将把它分解一下。

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

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

volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
  synchronized(lock){
    sharedValue=sharedValue+"something to add";
  }
}

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

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

另一种选择是使用 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);
}

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

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

于 2014-07-15T18:15:19.323 回答
39

这是 AtomicReference 的用例:

考虑充当数字范围的此类,并使用单独的 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());
    }
}

setLower 和 setUpper 都是 check-then-act 序列,但是它们没有使用足够的锁定来使它们成为原子的。如果数字范围为 (0, 10),并且一个线程调用 setLower(5) 而另一个线程调用 setUpper(4),则在一些不幸的时机下,两者都将通过设置器中的检查,并且将应用这两个修改。结果是范围现在保持 (5, 4) 无效状态。因此,虽然底层 AtomicIntegers 是线程安全的,但复合类不是。这可以通过使用 AtomicReference 而不是使用单个 AtomicIntegers 作为上限和下限来解决。

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;
        }
    }
}
于 2015-05-29T10:00:20.647 回答
25

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

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

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

于 2015-06-12T12:30:42.820 回答
12

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

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

public void doSomethingUsingLambdas() {

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

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

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

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

事实上,您可以使用任何拥有引用的对象,甚至是只有一项的 Collection。但是,AtomicReference 非常适合。

于 2018-04-23T09:00:22.520 回答
9

我们什么时候使用 AtomicReference?

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

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

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

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

Executors定义用于启动和管理线程的高级 API。java.util.concurrent 提供的执行器实现提供了适合大规模应用的线程池管理。

并发集合使管理大型数据集合变得更加容易,并且可以大大减少同步的需求。

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

提供一个应该使用 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);

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

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

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

于 2016-05-20T06:05:53.803 回答
6

我不会多说。我尊敬的朋友们已经提供了宝贵的意见。本博客最后的完整运行代码应该消除任何混淆。一个多线程场景下的电影订座小程序。

一些重要的基本事实如下。1> 不同的线程只能争用堆空间中的实例和静态成员变量。2> 易失性读取或写入完全是原子的并且序列化/发生在之前并且仅从内存中完成。通过这样说,我的意思是任何读取都将遵循内存中的先前写入。并且任何写入都将遵循先前从内存中读取的内容。因此,任何使用 volatile 的线程都将始终看到最新的值。 AtomicReference 使用了 volatile 的这个属性。

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

private volatile V value;

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

public final V get()

以下是 AtomicReference 最重要的方法。

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

compareAndSet(expect,update) 方法调用 Java 的 unsafe 类的 compareAndSwapObject() 方法。这个不安全的方法调用调用了本地调用,它调用了一条到处理器的指令。“期望”和“更新”每个都引用一个对象。

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

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

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();
        }

以下是完整的运行代码。

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

}    
于 2018-05-31T05:54:22.280 回答
-1

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

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

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

于 2015-07-15T13:02:43.960 回答