Java 线程之间的可见性是否需要原子引用?
我使用的框架在发送请求时需要回调。每个回调都必须实现这个接口。回调中的方法是异步调用的Java 线程之间的可见性是否需要原子引用?,java,multithreading,Java,Multithreading,我使用的框架在发送请求时需要回调。每个回调都必须实现这个接口。回调中的方法是异步调用的 public interface ClientCallback<RESP extends Response> { public void onSuccessResponse(RESP resp); public void onFailureResponse(FailureResponse failure); public void onError(Throwable e); }
public interface ClientCallback<RESP extends Response>
{
public void onSuccessResponse(RESP resp);
public void onFailureResponse(FailureResponse failure);
public void onError(Throwable e);
}
公共接口ClientCallback
{
成功响应的公共无效(RESP RESP);
失效响应公共无效(失效响应失效);
公共无效申报人(可丢弃);
}
要使用TestNG编写集成测试,我需要一个阻塞回调。所以我使用倒计时锁存器在线程之间进行同步
这里真的需要AtomicReference还是原始引用可以吗?我知道如果使用原始引用和原始整数(而不是CountDownLatch),代码将无法工作,因为无法保证可见性。但是由于倒计时锁存器已经同步,我不确定是否需要AtomicReference的额外同步。
注意:结果类是不可变的
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();
}
public类BlockingCallback实现ClientCallback
{
私有最终原子引用_result=新原子引用();
专用最终倒计时闩锁_闩锁=新倒计时闩锁(1);
成功响应(RESP RESP)上的公共无效
{
_set(新结果(resp,null,null));
_倒计时();
}
失效响应上的公共无效(失效响应失效)
{
_set(新结果(null,failure,null));
_倒计时();
}
公共无效申报人(可丢弃的e)
{
_set(新结果(null,null,e));
_倒计时();
}
公共结果getResult(最终长超时,最终时间单位)抛出InterruptedException,TimeoutException
{
如果(!\u闩锁等待(超时,单位))
{
抛出新的TimeoutException();
}
返回_result.get();
}
您不需要使用其他同步对象(AtomicRefetence)这里。重点是变量是在一个线程中调用CountDownLatch之前设置的,在另一个线程中调用CountDownLatch之后读取。CountDownLatch已经执行线程同步并调用内存屏障,因此保证了写入之前和读取之后的顺序。因此,您甚至不需要使用volatile对于该字段。为了使分配在线程之间可见,必须跨越某种内存障碍。这可以通过几种不同的方式完成,具体取决于您正试图做什么
- 您可以使用
字段。对volatile
字段的读取和写入是原子的,可以跨线程看到volatile
- 您可以使用
。这是有效的,但它更灵活(您可以重新分配和传递对原子引用
)的引用),并且有一些额外的操作,如原子引用
- 您可以使用
或类似的类,但您需要密切注意它们提供的内存不变量。例如,CountDownLatch
,可以保证所有CountDownLatch
线程将看到调用wait()
直到countDown()
被调用countDown()的线程中发生的一切
- 您可以使用
块。这些块更加灵活,但需要更加小心-写入和读取必须synchronized
同步,否则可能看不到写入
- 您可以使用线程安全的集合,例如
。如果您只需要跨线程引用,那么就可以使用Overkill,但对于存储多个线程需要访问的结构化数据非常有用ConcurrentHashMap
这并不是一个完整的选项列表,但希望您能看到有几种方法可以确保值对其他线程可见,
AtomicReference
只是其中一种机制。简而言之,这里不需要AtomicReference。不过您需要volatile
原因是您只对引用(结果)进行写入和读取,而不执行任何类似于compareAndSet()的复合操作
对于引用变量和大多数基本变量(除long和double之外的所有类型),读取和写入都是原子的。
参考,
Sun Java教程还有JLS(Java语言规范)
对引用的写入和读取始终是原子的,无论它们是作为32位值还是64位值实现。 Java 8
Java 7
Java 6
来源:
原子操作不能交错,因此可以在不担心线程干扰的情况下使用。但是,这并不能消除同步原子操作的所有需要,因为内存一致性错误仍然存在。使用易失性变量可以降低内存一致性错误的风险,因为对易失性变量的任何写入都会导致错误a清除与同一变量的后续读取之前发生的关系。这意味着其他线程始终可以看到对易失性变量的更改。此外,这还意味着,当线程读取易失性变量时,它不仅可以看到对易失性的最新更改,还可以看到导致该更改的代码的副作用把零钱调高 因为您只有一个写/读操作,而且它是原子操作,所以将变量设置为volatile就足够了 关于CountDownLatch的使用,它用于等待其他线程中的n个操作完成。因为您只有一个操作,所以可以使用Condition,而不是CountDownLatch 如果您对AtomicReference的使用感兴趣,您可以在实践中检查Java并发性(第326页),找到下面的书: 或者@Binita Bharti在下面的StackOverflow答案中使用的相同示例
一个好的起点是(我的重点): 内存一致性影响:在计数达到零之前,调用前线程中的操作