2016-03-04 18 views
3

我正在看一些在Java 6及更高版本中導致問題(死鎖)的代碼,但不是在Java 1.5中。Java 6同步中的更改?

BMP豆:

private MyClass m_c; 
public String ejbCreate(String id) throws CreateException, MyException 
{ 
    try 
    { 
     m_c = Singleton.getInstance().getObj(id); 
    } 
    catch (MyException e) 
    { 
     synchronized (Singleton.getInstance()) 
     { 
      //check again 
      if (!Singleton.getInstance().hasObj(id)) { 
       m_c = new MyClass(id); 
       Singleton.getInstance().addObj(id, m_c); 
      } 
      else { 
       m_c = Singleton.getInstance().getObj(id); 
      } 
     } 
    } 
} 

辛格爾頓:

private Map objCache = new HashMap(); 
private static Singleton INSTANCE = new Singleton(); 
public static Singleton getInstance() { 
    return INSTANCE; 
} 
public void addObj(String id, MyClass o) 
{ 
    if (this.objCache.containsKey(id)) { 
     this.objCache.remove(id); 
    } 
    this.objCache.put(id, o); 
} 

public MyClass getObj(String id) throws Exception 
{ 
    MyClass o = null; 
    o = (MyClass)this.objCache.get(id); 
    if (o == null) { 
     throw new MyException("Obj " +id+ " not found in cache"); 
    } 
    return o; 
} 

public boolean hasObj(String id) 
{ 
    return this.objCache.containsKey(id); 
} 

的證據更表明,將同步圓整的try/catch解決了使用Java 6

死鎖時顯然可以有一個或多個線程調用

Singleton.getInstance().getObj(id) 

沒有獲得鎖而另一個線程擁有鎖並正在執行同步塊中的代碼,但即使在考慮JSR-133中詳述的內存同步之後,它看起來並不像這種情況下應該有任何問題。

我知道我沒有解釋說這是一個僵局,而且它不是很理想的只繪製一張圖片,但繪製整個圖片需要一個非常大的畫布。

我已經看過Java 6發行版的註釋,並且唯一與聲音相關的區域圍繞着無爭用的同步,但我不知道在這種情況下這是否意義重大。

謝謝你的幫助。

+2

得到你需要一個僵局,然後再嘗試一次在不同的線程來獲得兩個或更多的鎖。單獨一個鎖就不會出現死鎖。你能否展示使用另一個鎖以及如何以不同的順序獲得這些鎖? –

+1

注:還有一個問題,你可以進入一個無限循環,如果你更新以不安全的方式一個地圖。這一直在那裏。我會考慮整個代碼線程安全的,如果你需要的併發訪問,使用的ConcurrentMap(Java 5.0中加入) –

+1

順便說一句,如果你有一個真正的僵局,這一切都使用內置'synchronized'關鍵字,那麼一個線程轉儲會實際檢測到這個死鎖並告訴你。如果你沒有看到,這是彼得理論的有力證據。 – yshavit

回答

4

我懷疑你沒有得到一個死鎖(在兩個不同的線程持有兩個不同的線程鎖),而是進入一個無限循環。如果您以非線程安全的方式訪問HashMap,則可能發生這種情況。用於處理衝突的鏈接列表中會發生什麼情況,並且讀者會永遠運行。這一直是一個問題,儘管Java 6中的一些細微差別可能會在不同版本不同時出現這個問題。

我建議你修復這個類,以便它使用線程安全集合,並且不會在Exception上重試,因爲不能保證會發生這種情況。

有很多,你可以做些什麼來改善這一類,但你真正需要的是ConcurrentMap.computeIfAbsent在Java中添加8

注:沒有理由

  • 檢查一個鍵是否存在在嘗試刪除它之前。
  • 在試圖放置它之前刪除一個鍵。
  • 拋出異常而不是返回null。
  • 當您可以將它傳遞給工廠時返回null。 (根據computeIfAbsent)
  • 當類型被預先知道時使用工廠。

我建議你

  • 使用的ConcurrentMap的線程安全的併發訪問。
  • 對單身人士使用enum

這兩個Java 5.0中加入。

public enum MyClassCache { 
    INSTANCE; 

    private final Map<String, MyClass> cache = new ConcurrentHashMap<>(); 

    public boolean hasId(String id) { 
     return cache.containsKey(id); 
    } 

    public MyClass get(String id) throws IllegalStateException { 
     MyClass ret = cache.get(id); 
     if (ret == null) throw new IllegalStateException(id); 
     return ret; 
    } 

    public MyClass getOrCreate(String id) throws IllegalStateException { 
     MyClass ret = cache.get(id); 
     if (ret == null) { 
      synchronized (cache) { 
       ret = cache.get(id); 
       if (ret == null) { 
        cache.put(id, ret = new MyClass(id)); 
       } 
      } 
     } 
     return ret; 
    } 
} 

在Java 8,你可以使用computeIfAbsent

public MyClass getOrCreate(String id) { 
    return cache.computeIfAbsent(id, MyClass::new); 
} 
+0

嗨,彼得。謝謝你的回覆。這絕對是我們看到的一個死鎖 - 一個org.jboss.util.deadlock.ApplicationDeadlockException。我們已經看了看線程,它確實是一個經典的死鎖 - 線程1擁有並希望B,線程2具有B和希望A. – Paul

+0

@保羅你能告訴我們這是死鎖兩個代碼路徑。 –

+2

@保羅:在你所示的代碼,只有一個鎖涉及,所以如果有涉及到兩個鎖陷入僵局,你的問題是不完整的,最好的。 – Holger

1

我說得對,這個問題的核心的區別是:

public void ejbCreate1(String id) throws Exception { 
    try { 
     m_c = Singleton.getInstance().getObj(id); 
    } catch (Exception e) { 
     synchronized (Singleton.getInstance()) { 
      //check again 
      if (!Singleton.getInstance().hasObj(id)) { 
       m_c = new MyClass(id); 
       Singleton.getInstance().addObj(id, m_c); 
      } else { 
       m_c = Singleton.getInstance().getObj(id); 
      } 
     } 
    } 
} 

public void ejbCreate2(String id) throws Exception { 
    synchronized (Singleton.getInstance()) { 
     try { 
      m_c = Singleton.getInstance().getObj(id); 
     } catch (Exception e) { 
      //check again 
      if (!Singleton.getInstance().hasObj(id)) { 
       m_c = new MyClass(id); 
       Singleton.getInstance().addObj(id, m_c); 
      } else { 
       m_c = Singleton.getInstance().getObj(id); 
      } 
     } 
    } 
} 

在Java-6中可能會導致第一個掛起,第二個正常工作。

顯然,主要區別在於,getObj可能由兩個不同的線程在同一時間被調用,而另一個線程被創建新對象甚至可以調用。

Is it safe to get values from a java.util.HashMap from multiple threads (no modification)?很可能,你是不是在這種情況下。結論是,一個線程從Map(也許o = (MyClass) this.objCache.get(id);)readng而另一個是通過調用addObj寫入地圖。這顯然是讀取崩潰和燒燬的祕訣。

有關潛在落水洞詳見Is a HashMap thread-safe for different keys?