本文翻译自:When to use AtomicReference in Java?
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。
#1楼
参考:https://stackoom.com/question/gDgT/何时在Java中使用AtomicReference
#2楼
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. 一些示例包括Long和Double的包装类,以及String,仅举几个例子。 (According to Programming Concurrency on the JVM immutable objects are a critical part of modern concurrency). (根据JVM不变对象上的编程并发性,这是现代并发性的关键部分)。
Next, why AtomicReference is better than a volatile object for sharing that shared value. 接下来,为什么AtomicReference优于易失的对象共享该共享的值。 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. 那是因为大多数锁都需要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. 但是在AtomicRefrence的幕后发生了一件非常重要的事情,那就是比较和交换。 It is a single CPU instruction, not an OS call, that makes the switch happen. 使切换发生的是单个CPU指令,而不是OS调用。 That is a single instruction on the CPU. 那是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. 对于AtomicReferences,要注意的是,它不使用.equals()调用,而是对期望值进行==比较。 So make sure the expected is the actual object returned from get in the loop. 因此,请确保期望的是从get循环返回的实际对象。
#3楼
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. setLower和setUpper都是先检查后执行的序列,但是它们没有使用足够的锁定使其成为原子。 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. 如果数字范围保持(0,10),并且一个线程调用setLower(5),而另一个线程调用setUpper(4),则在一些不幸的时间安排下,两个线程都将通过设置程序中的检查,并且将应用这两个修改。 The result is that the range now holds (5, 4)an invalid state. 结果是该范围现在保持(5,4)无效状态。 So while the underlying AtomicIntegers are thread-safe, the composite class is not. 因此,尽管底层的AtomicIntegers是线程安全的,但复合类不是。 This can be fixed by using a AtomicReference instead of using individual AtomicIntegers for upper and lower bounds. 可以通过使用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
}
#4楼
You can use AtomicReference when applying optimistic locks. 应用乐观锁时可以使用AtomicReference。 You have a shared object and you want to change it from more than 1 thread. 您有一个共享库,并且想要从多个线程中更改它。
- You can create a copy of the shared object 您可以创建共享库的副本
- Modify the shared object 修改共享对象
- 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 这是AtomicReference可以提供帮助的地方
#5楼
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
#6楼
When do we use AtomicReference? 我们什么时候使用AtomicReference?
AtomicReference is flexible way to update the variable value atomically without use of synchronization. AtomicReference是一种灵活的方式,可以自动更新变量值,而无需使用同步。
AtomicReference
support lock-free thread-safe programming on single variables. AtomicReference
支持对单个变量进行无锁线程安全编程。
There are multiple ways of achieving Thread safety with high level concurrent API. 有多种使用高级并发 API实现线程安全的方法。 Atomic variables is one of the multiple options. 原子变量是多个选项之一。
Lock
objects support locking idioms that simplify many concurrent applications. Lock
对象支持简化许多并发应用程序的锁定习惯用法。
Executors
define a high-level API for launching and managing threads. Executors
定义了用于启动和管理线程的高级API。 Executor implementations provided by java.util.concurrent provide thread pool management suitable for large-scale applications. java.util.concurrent提供的执行程序实现提供适用于大规模应用程序的线程池管理。
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. 提供一个简单的示例,其中应使用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 AtomicReference
in all multi threaded programs. 您不必在所有多线程程序中都使用AtomicReference
。
If you want to guard a single variable, use AtomicReference
. 如果要保护单个变量,请使用AtomicReference
。 If you want to guard a code block, use other constructs like Lock
/ synchronized
etc. 如果您想保护一个代码块,请使用其他结构,例如Lock
/ synchronized
等。