AtomicInteger是一个很好的解决方案,为多线程应用程序提供计数器吗?

时间:2009-11-04 07:18:06

标签: java android multithreading concurrency

我有一个Android客户端,它将与服务器建立Http连接。

服务器要求所有Http请求在Http头中提供单调递增的计数器。 e.g。

POST /foo/server
X-count: 43

将启动Http连接的地方:

  1. 用户命令下的内部活动,例如按钮点击
  2. 服务内部(由Context#startService启动)
  3. 为了显示计数器值,我计划在AtomicInteger子类中托管Application。然后,所有代码将从中心位置检索计数。

    如果Http连接失败(例如服务器关闭),我需要递减计数器。

    你认为AtomicInteger非常适合我的场景吗?

3 个答案:

答案 0 :(得分:12)

AtomicInteger正是您想要用于此目的的。

答案 1 :(得分:2)

  

如果Http连接失败(例如服务器关闭),我需要递减计数器。

我打算说“地狱好”,但在这句话后我不太确定。我认为你想做这样的事情:

def sendRequest(url)
    request = new request to url
    request.header["X-count"] = next serial
    if request.send() != SUCCESS
        rewind serial

在这种情况下,我猜不应该允许两个线程同时发送请求,然后你想要一些序列化请求而不是AtomicInteger的东西,它实际上只是让你以原子方式执行一些操作。如果两个线程同时调用sendRequest,并且第一个线程将失败,则会发生这种情况:

Thread  |  What happens?
--------+-------------------------
A       |  Creates new request           
B       |  Creates new request           
A       |  Set request["X-count"] = 0    
A       |  Increment counter to 1        
A       |  Send request
B       |  Set request["X-count"] = 1    
B       |  Increment counter to 2        
B       |  Send request
A       |  Request fails                 
B       |  Request succeeds              
A       |  Rewind counter down to 1      
C       |  Creates new request
C       |  Set request["X-count"] = 1
C       |  Increment counter to 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计数值。

编辑许多人似乎都担心上述解决方案无法并发运行。它没有。那是对的。但它需要以这种方式解决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); 
        }
    }

}

答案 2 :(得分:1)

gustafc是对的!要求

  

如果Http连接失败(例如服务器关闭),我需要递减计数器。

会破坏并发的可能性!

如果您希望HTTP标头AtomicInteger的唯一计数器是好的,但您无法从多个服务器或JVM发送请求,并且您需要允许漏洞。
因此,由于计数是徒劳的(就像在高度可扩展的环境中一样),使用UUID是一种更“可扩展”且更强大的解决方案。人类需要数数,机器不要该死的! 并且,如果您希望成功发送后成功的总数增加一个计数器(您甚至可以跟踪失败/成功UUID请求)。

关于并行计数我的2 cts:)