2009-11-04 83 views
2

我有一个Android客户端,将使Http连接到服务器。AtomicInteger是一个很好的解决方案,提供一个多线程应用程序的计数器?

服务器要求所有的Http请求在Http头中提供一个单调递增的计数器。例如

POST /foo/server 
X-count: 43 

地方将启动HTTP连接:

  1. 内活动用户的命令下,例如按钮点击
  2. 内部服务(由Context#startService启动)

抛出计数器值,我计划在我Application子类来承载AtomicInteger。所有的代码将从中心位置检索计数。

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

您认为AtomicInteger适合我的场景吗?

回答

11

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

+3

但是我的2克拉,你应该注意,其中具有较高计数的HTTP请求是一个与之前的较低值发送的情况下(如情况下,其中的低线计数HTTP请求结束睡眠) – notnoop 2009-11-04 07:29:42

+1

notnoop是正确的。如果你想在服务器端进行预定义排序(例如,发生 - 之前),你必须实现某种Lamport时钟。 – 2009-11-04 15:40:12

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

} 
+0

经验法则是使用锁定(无论是固有的还是通过锁定界面)尽可能短的时间。您的建议将使用通过整个网络发送的锁。尽管外来人同步调用,网络延迟可能会导致意外的延迟。 – 2009-11-04 14:13:37

+0

我没有看到在您的示例中发送了X-count = 1的两个** sucessfull **请求。其中两人被派出,但其中只有一人获胜。 – 2009-11-04 15:37:43

+0

@John W. - 据我了解的OP,请求必须按顺序发送。或者至少这是一个合理的结论,如果他们都需要他们自己的,独特的,连续的X计数值。 – gustafc 2009-11-04 16:18:14

1

gustafc是对的!要求

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

杀死任何并发的可能性!

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

并行计数:)

+0

我更关心尽管与gustafc的评论并发。如果服务器停机怎么办?然后,当线程调用SerialRequeste.sendRequest时,该方法将阻塞所有线程,直到发送请求返回超时执行为止。对于任何类型的吞吐量来说,这都是一个杀手锏。 – 2009-11-04 16:06:49

+1

@John W. - 我同意,如果你没有设置合适的超时时间,服务器停止工作或管道阻塞,我的解决方案很容易冻结应用程序,但事情是这样的它是*正确的* - 它执行OP要求的操作,这恰好要求按顺序发送请求。 – gustafc 2009-11-04 16:37:58

相关问题