Answers:
原子引用应在需要对引用执行简单原子(即线程安全,非平凡)操作的环境中使用,这种情况不适用于基于监视器的同步。假设仅当对象的状态保持在上次检查时的状态,您才想检查是否有特定的字段:
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位类型(例如long
或double
可能不是原子的);但是更新引用始终是原子的,即使它是64位的) ),而无需显式使用Atomic*
。
参见Java语言规范3ed,第17.7节。
AtomicReference
变量,则应标记该变量,volatile
因为在运行时保证引用分配是原子的时,编译器可能会在假定变量未被其他线程修改的情况下执行优化。
AtomicReference
”;如果您正在使用它,那么我的建议是朝相反的方向进行标记,final
以便编译器可以进行相应的优化。
当您需要在多个线程之间共享和更改不可变对象的状态时,原子引用是理想的选择。那是一个非常密集的陈述,所以我将其分解。
首先,不可变的对象是在构造后实际上不会改变的对象。通常,不可变对象的方法会返回同一类的新实例。一些示例包括Long和Double的包装类,以及String,仅举几个例子。(根据JVM不变对象上的编程并发性,这是现代并发性的关键部分)。
接下来,为什么AtomicReference在共享该共享值方面胜于易失性对象。一个简单的代码示例将显示差异。
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
每次您想要基于其易失性字段修改该易失性字段引用的字符串时,首先需要获得对该对象的锁定。这样可以防止其他线程在此期间进入并在新字符串串联的中间更改值。然后,当您的线程恢复时,您将破坏另一个线程的工作。但老实说,该代码可以正常工作,看起来很整洁,并且可以使大多数人满意。
轻微问题。太慢了 尤其是当该锁对象存在很多争用时。那是因为大多数锁都需要OS系统调用,并且您的线程将被阻塞并被上下文切换到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指令,而不是OS调用。那是CPU上的一条指令。而且由于没有锁,因此在执行锁的情况下不会进行上下文切换,这可以节省更多时间!
对于AtomicReferences,要注意的是,它不使用.equals()调用,而是对期望值进行==比较。因此,请确保期望的是从get循环返回的实际对象。
worked
获取相同的语义。
这是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),则在一些不幸运的时间安排下,两个线程都将通过设置程序中的检查,并且将应用两个修改。结果是该范围现在保持(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;
}
}
}
这是一个非常简单的用例,与线程安全无关。
要在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的线程都将始终看到最新的值。 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()方法。这种不安全的方法调用会调用本地调用,该本地调用会向处理器调用一条指令。“期望”和“更新”均引用一个对象。
仅当AtomicReference实例成员变量“值”所引用的对象相同时,才将“更新”分配给该实例变量,并返回“ 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
}
}
}
}
我们什么时候使用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
etc。
另一个简单的示例是在会话对象中进行安全线程修改。
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