何时在Java中使用AtomicReference?
我们什么时候使用AtomicReference?
是否需要在所有multithreading程序中创build对象?
提供一个使用AtomicReference的简单示例。
应该在需要对引用进行简单primefaces操作(即线程安全,非平凡)操作的设置中使用primefaces引用,对于这种引用,基于监视器的同步是不合适的。 假设你想检查一下特定的字段是否只有在上次检查时对象的状态仍然存在:
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);
由于primefaces引用语义,即使cache
对象在线程之间共享,也可以执行此操作,而不使用synchronized
。 一般来说,除非你知道自己在做什么,否则最好使用同步器或者java.util.concurrent
框架而不是纯粹的Atomic*
。
两个优秀的死树参考资料将向您介绍这个主题: Herlihy优秀的多处理器编程艺术和实践中的Java并发 。
请注意(我不知道这是否一直是真的)引用赋值(即=
)本身是primefaces的(更新原语64位types(long / double)可能不是primefaces的;但更新引用总是primefaces的,即使它是64位),而不明确使用Atomic*
。 参见JLS 3ed,第17.7节 ,
当需要共享和更改多个线程之间的不可变对象的状态时,使用primefaces引用是理想的。 这是一个超级密集的声明,所以我会分解一下。
首先,一个不可变对象是一个build造后实际上不会改变的对象。 通常,不可变对象的方法会返回同一类的新实例。 一些例子包括Long和Double的包装类,以及String,仅举几例。 (根据编程对JVM的并发性不可变对象是现代并发的重要组成部分)。
接下来,为什么AtomicReference比共享该共享值的volatile对象更好。 一个简单的代码示例将显示不同之处。
volatile String sharedValue; static final Object lock=new Object(); void modifyString(){ synchronized(lock){ sharedValue=sharedValue+"something to add"; } }
每当你想修改被这个volatile字段引用的string的时候,你首先需要获取该对象的一个锁。 这样可以防止其他线程在此期间进入,并在新string连接的中间更改值。 那么当你的线程恢复时,你会打断另一个线程的工作。 但老实说,代码将起作用,它看起来很干净,而且会让大多数人感到高兴。
轻微问题。 这很慢。 特别是如果这个锁对象有很多的争用。 那是因为大多数锁需要一个OS系统调用,并且你的线程将被阻塞,并被从CPU上下文切换到其他进程。
另一种select是使用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); }
现在为什么这样更好? 老实说,代码是比以前less一点干净。 但是在AtomicRefrence中有一些非常重要的事情,那就是比较和交换。 这是一个单一的CPU指令,而不是一个OS调用,使交换机发生。 这是CPU上的单个指令。 而且由于没有锁,所以在执行锁的情况下没有上下文切换,这节省了更多的时间!
对于AtomicReferences来说,这个方法并不使用.equals()调用,而是对期望值进行==比较。 所以确保预期的是从循环中返回的实际对象。
这是一个AtomicReference的用例:
考虑这个类作为一个数字范围,并使用单独的AtmomicIntegervariables来维持数字的下限和上限。
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序列,但是它们没有使用足够的locking来使它们成为primefaces。 如果数字范围成立(0,10),一个线程调用setLower(5),而另一个线程调用setUpper(4),有一些不幸的时间都会通过setters中的检查,两个修改都会被应用。 结果是范围现在保持(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 }
应用乐观锁时,您可以使用AtomicReference。 你有一个共享对象,你想从多于一个线程中改变它。
- 您可以创build共享对象的副本
- 修改共享对象
- 您需要检查共享对象是否与以前一样 – 如果是,则使用修改后的副本进行更新。
由于其他线程可能已经修改它,并且/可以在这两个步骤之间进行修改。 你需要做一个primefaces操作。 这是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是一种灵活的方式,可以在不使用同步的情况下自动更新variables值。
AtomicReference
支持单variables的无锁线程安全编程。
根据oracle文档页面,使用高级并发 API有多种方式实现线程安全。 使用primefacesvariables是多种select之一。
Lock
对象支持简化许多并发应用程序的locking方式。
Executors
定义了一个用于启动和pipe理线程的高级API。 由java.util.concurrent提供的执行器实现提供了适用于大规模应用程序的线程池pipe理。
Concurrent collections
可以更容易地pipe理大量的数据集合,并且可以大大减less同步的需要。
Atomic variables
具有最小化同步的function,有助于避免内存一致性错误。
提供一个使用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);
是否需要在所有multithreading程序中创build对象?
所有multithreading程序中不必使用AtomicReference
。
如果你想守护一个variables,使用AtomicReference
。 如果你想守护一个代码块,使用其他结构像Lock
/ synchronized
等