我正在使用一个在发送请求时需要回调的框架。每个回调都必须实现此接口。回调中的方法是异步调用的。
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();
}
答案 0 :(得分:2)
此处不需要使用其他同步对象(AtomicRefetence)。关键是该变量在一个线程中调用CountDownLatch之前设置,并在另一个线程中调用CountDownLatch之后读取。 CountDownLatch已经执行线程同步并调用内存屏障,因此保证了之前和之后的写入顺序。因此,您甚至不需要为该字段使用volatile。
答案 1 :(得分:1)
一个很好的起点是javadoc(强调我的):
内存一致性影响: 在计数达到零 之前,在调用
countDown()
之前的某个线程中的操作发生在操作之前在另一个线程中从相应的await()
成功返回后。
现在有两种选择:
onXxx
setter方法(即你只调用其中一个方法)并且你不需要任何额外的同步如果您在方案2中,则需要至少volatile
变量(在您的示例中不需要AtomicReference
)。
如果你在场景1中,你需要决定你想要的防守程度:
volatile
最后,在方案1中,您可能希望强制执行以下事实:只能调用一次setter,在这种情况下,您可能会使用onXxx
及其AtomicReference
方法来确保事先引用为null,否则抛出异常。
答案 2 :(得分:0)
为了使分配在线程之间可见,必须跨越某种类型的内存屏障。这可以通过几种不同的方式完成,具体取决于您尝试做什么。
volatile
字段。对volatile
字段的读取和写入是原子的,并且可以跨线程显示。AtomicReference
。这实际上是the same as a volatile
field,但它更灵活一些(您可以重新分配并传递对AtomicReference
的引用)并进行一些额外的操作,例如compareAndSet()
。CountDownLatch
或类似synchronizer类,但需要密切关注它们提供的内存不变量。例如,CountDownLatch
保证await()
的所有线程都会看到在调用countDown()
之前调用countDown()
的线程中发生的所有内容。synchronized
块。这些更灵活,但需要更加小心 - 写入和读取必须是synchronized
,否则可能看不到写入。ConcurrentHashMap
。如果你需要的只是一个跨线程引用,那就太过分了,但是对于存储多线程需要访问的结构化数据很有用。这并不是一个完整的选项列表,但希望您可以看到有几种方法可以确保某个值对其他线程可见,并且AtomicReference
只是其中一种机制
答案 3 :(得分:0)
简短的回答你不这里需要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并发(页326),找到以下书籍:
https://github.com/HackathonHackers/programming-ebooks/tree/master/Java
或@Binita Bharti在下面的StackOverflow回答中使用的相同示例
When to use AtomicReference in Java?