AtomicInteger 是为多线程应用程序提供计数器的好解决方案吗?
Posted
技术标签:
【中文标题】AtomicInteger 是为多线程应用程序提供计数器的好解决方案吗?【英文标题】:Is AtomicInteger a good solution to provide a counter for multithreaded app? 【发布时间】:2010-12-12 22:20:09 【问题描述】:我有一个 android 客户端,它将与服务器建立 Http 连接。
服务器要求所有 Http 请求在 Http 头中提供一个单调递增的计数器。例如
POST /foo/server
X-count: 43
会发起 Http 连接的地方:
-
在用户命令下的内部活动,例如按钮点击次数
在服务内部(由
Context#startService
启动)
为了提供计数器值,我计划在我的 Application
子类中托管一个 AtomicInteger
。然后所有代码将从中心位置检索计数。
如果 Http 连接失败(例如服务器宕机),我需要减少计数器。
您认为 AtomicInteger 是否适合我的场景?
【问题讨论】:
【参考方案1】:AtomicInteger 正是您想要用于该目的的。
【讨论】:
但是,您应该注意在一个具有较低值的 HTTP 请求之前发送具有较高计数的 HTTP 请求的情况(例如,在较低计数的 HTTP 请求的线程最终休眠的情况下) notnoop 是对的。如果你想在服务器端保留排序(即发生前),你必须实现某种 Lamport 时钟。【参考方案2】:如果 Http 连接失败(例如服务器关闭),我需要减少计数器。
我本来想说“是的”,但在这句话之后我有点不太确定。我认为你想做这样的事情:
定义发送请求(网址) request = 对 url 的新请求 request.header["X-count"] = 下一个序列 如果 request.send() != 成功 倒带序列在这种情况下,我猜不应该允许两个线程同时发送请求,然后你想要序列化请求的东西而不是AtomicInteger
,它实际上只是让你原子地执行一些操作。如果两个线程同时调用sendRequest
,而第一个线程失败,就会发生这种情况:
现在,您已经发送了两个 X-count = 1 的请求。如果您想避免这种情况,您应该使用类似的东西(假设 Request
和 Response
是用于处理对 URL 的请求的类):
class SerialRequester
private volatile int currentSerial = 0;
public synchronized Response sendRequest(URL url) throws SomeException
Request request = new Request(url);
request.setHeader("X-count", currentSerial);
Response response = request.send();
if (response.isSuccess()) ++currentSerial;
return response;
该类保证没有两个成功的请求(通过相同的SerialRequester
发出)具有相同的 X-count 值。
编辑 许多人似乎担心上述解决方案不能同时运行。它没有。这是正确的。但它需要以这种方式解决 OP 的问题。现在,如果请求失败时计数器不需要递减,AtomicInteger
将是完美的,但在这种情况下是不正确的。
编辑 2 我得到它来编写一个不太容易冻结的串行请求程序(如上面的那个),这样如果请求等待时间过长(即排队),它就会中止请求在工作线程中但未启动)。因此,如果管道阻塞并且一个请求挂起很长时间,其他请求将最多等待固定的时间,因此队列不会无限增长,直到阻塞消失。
class SerialRequester
private enum State PENDING, STARTED, ABORTED
private final ExecutorService executor =
Executors.newSingleThreadExecutor();
private int currentSerial = 0; // not volatile, used from executor thread only
public Response sendRequest(final URL url)
throws SomeException, InterruptedException
final AtomicReference<State> state =
new AtomicReference<State>(State.PENDING);
Future<Response> result = executor.submit(new Callable<Response>()
@Override
public Result call() throws SomeException
if (!state.compareAndSet(State.PENDING, State.STARTED))
return null; // Aborted by calling thread
Request request = new Request(url);
request.setHeader("X-count", currentSerial);
Response response = request.send();
if (response.isSuccess()) ++currentSerial;
return response;
);
try
try
// Wait at most 30 secs for request to start
return result.get(30, TimeUnit.SECONDS);
catch (TimeoutException e)
// 30 secs passed; abort task if not started
if (state.compareAndSet(State.PENDING, State.ABORTED))
throw new SomeException("Request queued too long", e);
return result.get(); // Task started; wait for completion
catch (ExecutionException e) // Network timeout, misc I/O errors etc
throw new SomeException("Request error", e);
【讨论】:
经验法则是尽可能少地使用锁定(无论是内在的还是通过 Lock 接口)。您的建议将通过整个网络发送使用锁定。尽管调用了外星人同步调用,但网络延迟可能会导致意外延迟。 在您的示例中,我看不到使用 X-count=1 发送的两个 sucessfull 请求的位置。发送了两个,但只有一个成功。 @John W. - 据我了解 OP,请求必须按顺序发送。或者,如果他们都需要自己的、唯一的、连续的 X 计数值,那么至少这是一个合乎逻辑的结论。 @Miguel Ping - 线程 C 的请求是否成功并不重要。我们这里已经有两个错误:首先,线程 B 中的请求应该有 X-count = 0,因为前面的请求(在线程 A 中)没有成功。其次,线程 C 中的请求不应与来自 B 的 成功 请求具有相同的 X 计数,无论它是否成功。如果一个 X-count =n
的请求成功,那么以后的请求不应该有 X-count = n
- 或者至少,这就是我阅读 OP 的方式。
@Gustafc。我理解您为什么要确保那里的互斥,但我不建议实施。如果您有许多线程正在发送到许多不同的 URL,并且其中一些 URL 超时,那么任何尝试发送的线程都必须等到超时完成。请求本身是该方法的本地请求,因此等待线程不会知道问题是什么【参考方案3】:
gustafc 是对的!要求
如果 Http 连接失败(例如服务器宕机),我需要减少计数器。
消除任何并发的可能性!
如果你想要一个唯一的 HTTP 头计数器 AtomicInteger
很好,但是你不能从多个服务器或 JVM 发送请求,你需要允许漏洞。
因此,由于计数是徒劳的(就像在高度可扩展的环境中一样),所以使用 UUID
是一种更“可扩展”和健壮的解决方案。人类需要计数,机器不在乎!
而且,如果您希望在成功发送后增加一个计数器的成功总数(您甚至可以跟踪失败/成功的UUID
请求)。
我的 2 cts 平行计数 :)
【讨论】:
我更关心 gustafc 评论的并发性。如果服务器宕机了怎么办?然后任何时候线程调用 SerialRequeste.sendRequest 该方法将阻塞所有线程,直到发送请求返回超时执行。这对任何类型的吞吐量来说都是一个杀手 @John W. - 如果您没有设置适当的超时并且服务器出现故障或管道堵塞,我的解决方案很容易冻结应用程序,我同意,但问题在于它是正确 - 它执行 OP 的要求,恰好要求按顺序发送请求。以上是关于AtomicInteger 是为多线程应用程序提供计数器的好解决方案吗?的主要内容,如果未能解决你的问题,请参考以下文章