2009-11-04 28 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

相關問題