[英]Is AtomicReference needed for visibility between threads?
我正在使用一个在发送请求时需要回调的框架。 每个回调都必须实现此接口。 回调中的方法是异步调用的。
public interface ClientCallback<RESP extends Response>
{
public void onSuccessResponse(RESP resp);
public void onFailureResponse(FailureResponse failure);
public void onError(Throwable e);
}
要使用TestNG编写集成测试,我想要一个阻塞回调。 所以我使用CountDownLatch来在线程之间进行同步。
这里真的需要AtomicReference还是原始引用好吗? 我知道如果我使用原始引用和原始整数(而不是CountDownLatch),代码将无法工作,因为无法保证可见性。 但由于CountDownLatch已经同步,我不确定是否需要AtomicReference的额外同步。 注意:Result类是不可变的。
public class BlockingCallback<RESP extends Response> implements ClientCallback<RESP>
{
private final AtomicReference<Result<RESP>> _result = new AtomicReference<Result<RESP>>();
private final CountDownLatch _latch = new CountDownLatch(1);
public void onSuccessResponse(RESP resp)
{
_result.set(new Result<RESP>(resp, null, null));
_latch.countDown();
}
public void onFailureResponse(FailureResponse failure)
{
_result.set(new Result<RESP>(null, failure, null));
_latch.countDown();
}
public void onError(Throwable e)
{
_result.set(new Result<RESP>(null, null, e));
_latch.countDown();
}
public Result<RESP> getResult(final long timeout, final TimeUnit unit) throws InterruptedException, TimeoutException
{
if (!_latch.await(timeout, unit))
{
throw new TimeoutException();
}
return _result.get();
}
您不需要在此处使用其他同步对象(AtomicRefetence)。 关键是该变量在一个线程中调用CountDownLatch之前设置,并在另一个线程中调用CountDownLatch之后读取。 CountDownLatch已经执行线程同步并调用内存屏障,因此保证了之前和之后的写入顺序。 因此,您甚至不需要为该字段使用volatile。
一个很好的起点是javadoc (强调我的):
内存一致性影响: 在计数达到零之前,调用
countDown()
之前的线程中的操作发生在从另一个线程中的相应await()
成功返回之后的操作之前 。
现在有两种选择:
onXxx
setter方法(即你只调用其中一个方法)并且你不需要任何额外的同步 如果您在场景2中,则需要使变量至少为volatile
(在您的示例中不需要AtomicReference
)。
如果你在场景1中,你需要决定你想要的防御性:
volatile
onXxx
方法是可见 最后,在方案1中,您可能希望强制执行以下事实:只能调用setter一次,在这种情况下,您可能会使用AtomicReference
及其compareAndSet
方法来确保事先引用为null,否则抛出异常。
为了在线程之间显示赋值,必须跨越某种内存屏障。 这可以通过几种不同的方式完成,具体取决于您正在尝试做什么。
volatile
字段。 对volatile
字段的读取和写入在线程中是原子的和可见的。 AtomicReference
。 这实际上与volatile
字段相同 ,但它更灵活(您可以重新分配并传递对AtomicReference
引用)并且有一些额外的操作,比如compareAndSet()
。 CountDownLatch
或类似的同步器类,但您需要密切关注它们提供的内存不变量。 CountDownLatch
,例如,保证所有的线程await()
将看到发生在调用线程一切countDown()
最多时countDown()
被调用。 synchronized
块。 这些更加灵活,但需要更加小心 - 写入和读取都必须synchronized
,否则可能看不到写入。 ConcurrentHashMap
。 如果你需要的只是一个跨线程引用,那就太过分了,但是对于存储多线程需要访问的结构化数据很有用。 这不是一个完整的选项列表,但希望您可以看到有几种方法可以确保某个值对其他线程可见,并且AtomicReference
只是其中一种机制。
简短的回答是你在这里不需要AtomicReference。 你需要挥发性的。
原因是你只是写入和读取引用(Result)而不执行任何复合操作,如compareAndSet()。
读取和写入对于引用变量和大多数原始变量(除long和double之外的所有类型)都是原子的。
参考,Sun Java教程
https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
然后是JLS(Java语言规范)
对引用的写入和读取始终是原子的,无论它们是实现为32位还是64位值。
Java 8
http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7
Java 7
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7
Java 6
http://docs.oracle.com/javase/specs/jls/se6/html/memory.html#17.7
资料来源: https : //docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
原子动作不能交错,因此可以使用它们而不必担心线程干扰。 但是,这并不能消除所有同步原子操作的需要,因为仍然可能存在内存一致性错误。 使用volatile变量可降低内存一致性错误的风险,因为对volatile变量的任何写入都会建立与之后读取同一变量的先发生关系 。 这意味着对volatile变量的更改始终对其他线程可见。 更重要的是,它还意味着当线程读取volatile变量时,它不仅会看到volatile的最新更改,还会看到导致更改的代码的副作用。
由于您只有单个操作写入/读取且它是原子的,因此使变量volatile变得足够。
关于CountDownLatch的使用,它用于等待其他线程中的n个操作完成。 由于您只有一个操作,因此可以使用Condition而不是CountDownLatch。
如果您对AtomicReference的使用感兴趣,可以查看实践中的Java Concurrency(页326),找到以下书籍:
https://github.com/HackathonHackers/programming-ebooks/tree/master/Java
或者@Binita Bharti在下面的StackOverflow回答中使用的相同示例
何时在Java中使用AtomicReference?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.