2012-03-31 139 views
2

這些方法是getNewId() & fetchIdsInReserve()線程安全嗎?此方法線程安全嗎?

public final class IdManager { 

    private static final int NO_OF_USERIDS_TO_KEEP_IN_RESERVE = 200; 

    private static final AtomicInteger regstrdUserIdsCount_Cached = new AtomicInteger(100); 
    private static int noOfUserIdsInReserveCurrently = 0; 

    public static int getNewId(){     
      synchronized(IdManager.class){ 
       if (noOfUserIdsInReserveCurrently <= 20) 
         fetchIdsInReserve();  

       noOfUserIdsInReserveCurrently--; 
      } 
      return regstrdUserIdsCount_Cached.incrementAndGet(); 
    } 


    private static synchronized void fetchIdsInReserve(){ 
     int reservedInDBTill = DBCountersReader.readCounterFromDB(....); // read column from DB 

     if (noOfUserIdsInReserveCurrently + regstrdUserIdsCount_Cached.get() != reservedInDBTill) throw new Exception("Unreserved ids alloted by app before reserving from DB"); 

     if (DBUpdater.incrementCounter(....)) //if write back to DB is successful 
       noOfUserIdsInReserveCurrently += NO_OF_USERIDS_TO_KEEP_IN_RESERVE; 
    } 

} 
+0

'//從數據庫中取數據...'代碼是否容易被其他線程阻塞?特別是,其他線程已經開始做一些數據庫的東西,然後*然後*想獲得一個新的ID?如果是這樣,你可能會讓自己陷入僵局。 – cHao 2012-03-31 21:27:06

+0

,因爲對該方法的調用只是通過'getNewId()'中的同步塊來實現的。我相信這種方法一次只能被一個線程調用嗎? – 2012-03-31 21:29:00

+0

我假設這不是唯一的代碼,你會碰到一個數據庫...其他代碼可能會獲得鎖等。 – cHao 2012-03-31 21:30:25

回答

1

如果21線程來這裏

 synchronized(IdManager.class){ 
      if (noOfUserIdsInReserveCurrently <= 20) 
        fetchIdsInReserve();  

      noOfUserIdsInReserveCurrently--; 
     } 

,並等待另一個線程180繼續通過頂部,並通過線下,然後由當時的21線程達到下面的一行,會有儲備沒有用戶ID時,從第一組21線程調用

 return regstrdUserIdsCount_Cached.incrementAndGet(); 

編輯:

這裏有類負載的初始狀態:

regstrdUserIdsCount_Cached = 100 
noOfUserIdsInReserveCurrently = 0 

讓我們假設寫回DB總是成功的。如果不是這樣的話,這段代碼顯然會被破壞,因爲在這種情況下它仍然會分配一個ID。

第一個線程通過並調用fetch,因爲沒有保留的id。

regstrdUserIdsCount_Cached = 100 
noOfUserIdsInReserveCurrently = 0 

假設DB返回100作爲初始ID,該方法不爭完成現在

regstrdUserIdsCount_Cached = 101 
noOfUserIdsInReserveCurrently = 199 

後,假設178多個線程經歷不爭

regstrdUserIdsCount_Cached = 279 
noOfUserIdsInReserveCurrently = 21 

如果線程被另一個線程搶佔,在它退出​​塊之後但在遞減原子int之前,搶佔線索廣告會觸發抓取。

由於noOfUserIdsInReserveCurrently沒有被認爲是捷足先登線程遞減,

(noOfUserIdsInReserveCurrently + regstrdUserIdsCount_Cached.get() != reservedInDBTill) 

將是錯誤的。

假設異常表示失敗模式,那麼在其他交織期間不會拋出的一個交織過程中出現故障。因此,該代碼不是線程安全的。

解決方案是在關鍵部分內持續訪問regstrdUserIdsCount_Cached。在這種情況下,它不一定是一個原子整數,但可以簡單地是一個非整數。

+0

抱歉無法真正理解您的觀點..但您能否提出正確的解決方案?非常感謝! – 2012-03-31 21:40:42

+1

有多少線程來這裏無所謂。同步塊內的代碼一次由一個線程執行。 – 2012-03-31 21:45:52

+0

@EugeneRetunsky,true,但synchronized塊中的代碼檢查在原子整數更新之前是否有可用資源。由該前提保護的原子整數更新在synchronized塊之外。 – 2012-03-31 22:28:55

1

您現場noOfUserIdsInReserveCurrently不是從其他地方訪問,則是 - 訪問它是線程安全的。

P.S.如果僅在getNewId方法中的同步 塊內調用fetchIdsInReserve,則不必使該方法同步。

更新:只要該問題被編輯,現在它不是線程安全的。您必須在INSIDE同步塊的第一個方法中具有return語句。它不一定是AtomicInteger,在這種情況下它可以只是一個簡單的int

+0

'noOfUserIdsInReserveCurrently'也在'fetchIdsInReserve()'內被訪問,但是我相信這也是同步的,因爲它是從一個同步塊調用的嗎? – 2012-03-31 21:37:05

+0

沒錯。如果該字段僅在同步(IdManager.class)塊內訪問,或者該類的靜態同步方法 - 它是線程安全的。 – 2012-03-31 21:42:30

0

這是一個示例測試,以顯示它是線程安全的。用int替換AtomicInteger並擺脫同步,測試失敗。

public static void main(String... arg) throws Exception { 
final int loops = 1042; 
for (int l = 0; l != loops; l++) { 
    reset(); // add this method to your class to reset the counters 
    final int maxThreads = 113; 
    final ArrayList<String> checker = new ArrayList<String>(); 
    final CountDownLatch cl1 = new CountDownLatch(maxThreads); 
    final CountDownLatch cl2 = new CountDownLatch(maxThreads); 
    for (int x = 0; x != maxThreads; x++) { 
    Thread thread = new Thread(new Runnable() { 
     public void run() { 
     cl1.countDown(); 
     try { 
      cl1.await(); // stack all threads here 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     int id = getNewId(); 
     synchronized(checker) { 
      checker.add("" + id); 
     } 
     cl2.countDown(); 
     } 
    }); 
    thread.start(); 
    } 
    cl2.await(); 
    for (int x = 0; x != maxThreads; x++) { 
    String key = "" + (101 + x); // 1st ID value 
    if (!checker.contains(key)) { 
     System.out.println("Checker 1 FAIL - missing id=" + key); 
    } else { 
     checker.remove(key); 
     if (checker.contains(key)) { 
     System.out.println("Checker 2 FAIL - extra id=" + key); 
     } 
    } 
    } 
    for (int x = 0; x != checker.size(); x++) { 
    String key = "" + (101 + x); 
    System.out.println("Checker 3 FAIL - extra id=" + key); 
    } 
} 
} 
+1

你不能機械地證明正確性;您只能顯示錯誤的存在,或者特定情況不會導致錯誤出現。線程錯誤特別是非確定性的,因此容易避免每一次檢測的嘗試,直到它們可以造成最大的損害。 :) – cHao 2012-04-01 01:46:53