开发者

Is AtomicInteger a good solution to provide a counter for multithreaded app?

I have an android client which will make Http connections to a server.

The server requires that all Http request provide a monotonically increasing counter in the Http header. e.g.

POST /foo/server
X-count: 43

Places that will initiate Http connections:

  1. Inside activities under the user's command e.g. button clicks
  2. Inside a Service (started by Context#startS开发者_JAVA技巧ervice)

To dish out counter values, I plan to host an AtomicInteger in my Application subclass. All code will then retrieve a count from a central place.

If the Http connection fails (e.g. server is down), I need to decrement the counter.

Do you think AtomicInteger is a good fit for my scenario ?


AtomicInteger is exactly what you want to use for that purpose.


If the Http connection fails (e.g. server is down), I need to decrement the counter.

I was going to say "hell yes", but I'm somewhat less certain after this sentence. I take it you want to do something like this:

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

In that case, I'd guess that two threads should not be allowed to send requests simultaneously, and then you want something that serializes requests rather than an AtomicInteger, which really just lets you perform a few operations atomically. If two threads were to call sendRequest simultaneously, and the first one would fail, this would happen:

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

And now, you've sent two request with X-count = 1. If you want to avoid this, you should use something like (assume Request and Response are classes used to handle requests to URLs):

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;
    }

}

This class guarantees that no two successful requests (made through the same SerialRequester) have the same X-count value.

Edit Many seem concerned about the above solution not running concurrently. It doesn't. That's correct. But it needs to work this way to solve the OP's problem. Now, if the counter needn't be decremented when a request fails, an AtomicInteger would be perfect, but it's incorrect in this scenario.

Edit 2 I got it in me to write a serial requester (like the one above) less prone to freezing, such that it aborts requests if they've been pending too long (i.e., queued in the worker thread but not started). Thus, if the pipes clog and one request hangs for a very very long time, other requests will wait at most a fixed amount of time, so the queue doesn't grow indefinitely until the clog goes away.

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); 
        }
    }

}


gustafc is right! The requirement

If the Http connection fails (e.g. server is down), I need to decrement the counter.

kills any possibility of concurrency!

If you want a unique counter for the HTTP header AtomicInteger is good, but you cannot send requests from multiple servers or JVM, and you need to allow holes.
So, since counting is futile (like always in highly scalable environment) using a UUID is a more "scalable" and robust solution. Human needs to count, machine don't give a damn!
And, so if you want the total number of success increment a counter after the successful send (you can even keep track of failed/successful UUID requests).

My 2 cts on parallel counting :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜