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"] = 0 一个 |将计数器递增到 1 一个 |发送请求 乙|设置请求["X-count"] = 1 乙|将计数器递增到 2 乙|发送请求 一个 |请求失败 乙|请求成功 一个 |倒回计数器到 1 C |创建新请求 C |设置请求["X-count"] = 1 C |将计数器递增到 2

现在,您已经发送了两个 X-count = 1 的请求。如果您想避免这种情况,您应该使用类似的东西(假设 RequestResponse 是用于处理对 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 是为多线程应用程序提供计数器的好解决方案吗?的主要内容,如果未能解决你的问题,请参考以下文章

AtomicInteger在实际项目中的应用

java线程安全的整型类AtomicInteger

JAVA 中无锁的线程安全整数 AtomicInteger介绍和使用

如何在 mfc 中为多线程应用程序创建通用日志文件?

AtomicInteger

AtomicInteger