什么时候在Java中使用AtomicReference ?

什么时候使用AtomicReference?

在所有多线程程序中都需要创建对象吗?

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

238567 次浏览

原子引用应该在需要对引用执行简单的原子(即< em >线程安全的< / em >,非平凡)操作的设置中使用,对于这种情况,基于监视器的同步是不合适的。假设你只想在对象的状态在处理过程中发生改变时才设置一个特定的字段:

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节 . 0.

.

当您需要更新由多个线程访问的内容(在不可变对象中)时,使用原子引用是理想的,方法是将其替换为这些线程之间共享的新副本(不可变对象的副本)。这是一个非常密集的陈述,所以我要把它分解一下。

首先,不可变对象是指在构造后实际上没有改变的对象。通常,不可变对象的方法会返回同一类的新实例。举几个例子,包括包装器类LongDouble,以及String。(根据JVM上的并发编程,不可变对象是现代并发性的关键部分。)

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

volatile String sharedValue;


static final Object lock = new Object();


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

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

轻微的问题。它很慢。特别是当这个锁对象有很多争用的时候。这是因为大多数锁都需要OS系统调用,你的线程会阻塞,并被上下文切换出CPU,为其他进程让路。

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

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

问题是,对于AtomicReferences,它不使用.equals()调用,而是对期望的值进行==比较。所以要确保期望的是循环中get返回的实际对象。

下面是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都是检查-然后-行动序列,但是它们没有使用足够的锁定来使它们成为原子序列。如果数字范围保持不变(0,10),一个线程调用setLower(5),而另一个线程调用setUpper(4),在某些不幸的时机下,两个线程都将通过setter中的检查,两个修改都将被应用。结果是范围现在保持(5,4)无效状态。因此,虽然底层AtomicIntegers是线程安全的,但复合类不是。这可以通过使用AtomicReference来解决,而不是使用单独的atomicinteger作为上界和下界。

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

在应用乐观锁时可以使用AtomicReference。你有一个共享对象,你想从多个线程改变它。

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

因为其他线程可能已经修改了它,并且/可以在这两个步骤之间修改。你需要在原子操作中完成它。这就是AtomicReference可以提供帮助的地方

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

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

什么时候使用AtomicReference?

AtomicReference是一种不使用同步而自动更新变量值的灵活方法。

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

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

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

Executors定义了用于启动和管理线程的高级API。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等。

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

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

public void doSomethingUsingLambdas() {


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


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


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

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

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

我不会说太多。我尊敬的朋友们已经提出了宝贵的意见。本博客最后部分的完整运行代码应该可以消除任何困惑。这是一个多线程场景下的电影座位预订小程序。

一些重要的基本事实如下。 1比;不同的线程只能争夺堆空间中的实例变量和静态成员变量。 2比;Volatile读或写完全是原子的,并且是序列化的/在此之前发生,并且只从内存中完成。我这么说的意思是,在内存中,任何读操作都将跟随之前的写操作。任何写入都将跟随之前从内存中读取的操作。因此任何使用volatile的线程都会看到最新的值。 AtomicReference使用volatile属性。

下面是AtomicReference的一些源代码。 AtomicReference引用一个对象引用。这个引用是AtomicReference实例中的一个volatile成员变量,如下所示
private volatile V value;

Get()只是返回变量的最新值(就像volatile在" happens before"的方式)。

public final V get()

下面是AtomicReference最重要的方法。

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

compareAndSet(expect,update)方法调用Java不安全类的compareAndSwapObject()方法。不安全的方法调用调用本机调用,本机调用向处理器调用一条指令。“expect"和“;update"每个引用一个对象。

当且仅当AtomicReference实例成员变量"value"指的是同一个对象是由"expect", "update"现在分配给该实例变量,并且"true"返回。否则返回false。整个过程是自动完成的。没有其他线程可以在中间进行拦截。 由于这是一个单处理器操作(现代计算机体系结构的魔力),它通常比使用同步块更快。但记住,当需要原子地更新多个变量时,AtomicReference不起作用。 < / p >

我想添加一个完整的运行代码,它可以在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
}
}
}


}