2012-08-10 32 views
1

因爲我沒有發現涉及此主題的問題,所以我想我會將我的解決方案分享給以下方案。答案可能很明顯,但我花了很長的路要找出答案。 :)我會很感激問題和答案以及其他解決方案的反饋。在惰性加載Getter上同步

場景:

假設你有一個多線程的程序,並希望在你的程序對一些功能的數據庫連接(或其他一些共享對象),而你的程序的其他部分並不需要它所有。應該只有一個連接到數據庫,但。

與此同時,您想要檢測db連接丟失並嘗試重新連接。

爲了解決這個問題,你需要實現一個延遲加載模式「getter」,它在返回連接對象之前也檢查連接的有效性。

您的代碼可能是這樣的:

public class Main { 
    private DB _db; 

    public static void main(String[] args) { 
    new Main().start(); 
    } 

    private void start() { 
    // Program code goes here 
    // You create several threads, some of which may call getDB() whenever they need DB access 
    } 

    public DB getDB() { 
    if (_db == null) { 
     _db = getDBConnection(); 
    } else if (!_db.isConnectionValid()) { 
     /* 
     * DB connection is not valid anymore. Let's close it and 
     * try to get a new connection. 
     */ 
     _db.close(); 
     _db = getDBConnection(); 
    } 

    return _db; 
    } 

    private DB getDBConnection() { 
    DB db; 

    // Obtain a new connection... 
    ... 

    return db; 
    } 
} 

問題

的多個線程可能會試圖獲得在幾乎同一時間一個數據庫連接。當某些類保持對它們的引用時,甚至有可能多個連接共存。

回答

1

幾個線程可能會嘗試幾乎同時獲得數據庫連接。當某些類保持對它們的引用時,甚至有可能多個連接共存。

在這種情況下,您需要一個池,因爲您可以獲得多個不同的實例。有許多DatabaseConnection池可用,並且一些JDBC驅動程序有其自己的。我建議你使用JDBC驅動程序附帶的一個,或者使用C3P0等作爲數據庫連接池。

更具體地說,你需要採取的方式,另一個線程不能得到相同的連接的連接(不只是得到它)。一個簡單的例子是使用隊列。

private final Queue<DB> freeDBs = new ConcurrentLinkedQueue<>(); 

public DB acquireDB() { 
    DB db = freeDBs.poll(); 
    if (db != null && db.isConnectionValid()) 
     return db; 
    if (db != null) 
     db.close(); 
    return getDBConnection(); 
} 

public void release(DB db) { 
    if (freeDBs.size() >= MAX_FREE_SIZE) 
     db.close(); 
    else 
     freeDBs.add(db); 
} 
+0

這可以結合「信號量」類嗎?這樣做是否合理? – riha 2012-08-13 05:57:25

1

可以使用同步來避免同時創建多個連接。如果兩個(或更多)線程幾乎同時調用它,則其中一個線程會阻塞(等待),直到另一個線程完成。這可以確保第二個線程獲取剛剛由第一個線程創建的連接,而不是建立另一個連接。

我第一次嘗試這樣的對象同步:

public DB getDB() { 
    synchronized (_db) { 
    if (_db == null) { 
     _db = getDBConnection(); 
    } else if (!_db.isConnectionValid()) { 
     /* 
     * DB connection is not valid anymore. Let's close it and 
     * try to get a new connection. 
     */ 
     _db.close(); 
     _db = getDBConnection(); 
    } 
    } 

    return _db; 
} 

這裏的問題是,不與延遲加載工作。您無法同步null(您會收到NullPointerException),但在第一次呼叫getDB()時沒有任何對象。

的解決方案是對整個方法同步:

public synchronized DB getDB() { 
    if (_db == null) { 
    _db = getDBConnection(); 
    } else if (!_db.isConnectionValid()) { 
    /* 
    * DB connection is not valid anymore. Let's close it and 
    * try to get a new connection. 
    */ 
    _db.close(); 
    _db = getDBConnection(); 
    } 


    return _db; 
} 

此外,您需要確保沒有其他的方法來訪問私有字段_db或直接致電getDBConnection()。那將不再同步。

您的類不應該保留對連接的引用,因爲這樣可以防止死連接對象上的垃圾回收。通常不建議調用getter,因爲每個get都可能發出查詢來檢查連接有效性(取決於驅動程序)。如果每個方法在執行過程中都保留一個引用(除非它執行了很長時間),那很可能是正確的。

+2

所有這可能是罰款,一個學習的經驗,但你真的要在生產什麼是嚴重**連接池**執行,將採取的照顧問題,你在這裏指出,以及大範圍的其他您即將發現的問題。 '骨骼CP'是我目前最喜歡的。 – 2012-08-10 10:06:53

+0

您仍然有多個線程可以獲得相同連接的問題。 – 2012-08-10 10:10:59

1

那麼這裏是我的2C:

首先,關於你所使用的同步對象實例:如果您使用_db對象,在這個意義上是不好的,你不會得到你想要的。這裏的想法是確保如果多個線程嘗試同時創建一個_db實例(就JDK進程而言),一旦其中一個線程創建一個實例,其他線程應立即知道該實例是否存在,而不是嘗試創建另一個實例。現在,如果您在該實例上同步代碼塊,我們試圖在線程之間進行同步,即使所述實例永遠不會爲null,您仍然處於競爭狀態,兩個線程各自設法創建_db的實例,並且由於代碼塊在該實例上是同步的,所以沒有任何線程會被鎖阻塞,因爲確實有兩個單獨的鎖。 顯然,最好是同步整個方法。這相當於編寫

public DB getDB() { 
     synchronized (this) { 
      if (_db == null) { 
       _db = getDBConnection(); 
      } else if (!_db.isConnectionValid()) { 
       /* 
       * DB connection is not valid anymore. Let's close it and 
       * try to get a new connection. 
       */ 
       _db.close(); 
       _db = getDBConnection(); 
      } 
      return _db; 
     } 
    } 

的所有線程調用創建的_db實例的方法將「打」在同一個鎖(主類的實例),所以你可以肯定的是,一旦一個線程獲得該鎖其他人將阻塞,直到該線程完成,然後,當輪到他們執行該方法時,if檢查將阻止他們創建_db對象的第二個實例。現在

,另一個問題是天氣,你真的想在多個線程在同一_db實例。這個問題真的是簡化爲天氣_db是線程安全的,換句話說,它是無狀態的嗎?如果它是有狀態的並由多個線程共享,並且如果該狀態不能防範多線程調用,則會出現奇怪的行爲,甚至出現錯誤。例如:JDBC Connection對象不是線程安全的,因爲它包含事務的狀態,例如事務,如果多個線程同時訪問相同的JDBC Connection,可以無法改變這些事務。由於這個原因,建議在多線程環境中使用JDBC連接時使用一定程度的(對象實例)隔離。要麼你普通的舊爲每個線程新的JDBC連接實例或你做只有一個,但後來每個線程裏面將保留作爲一個ThreadLocal字段,以便每個線程真正得到他自己的實例,它只有他自己才改變/訪問。

另一個例子是HasmMap和ConcurrentHashMap的。在這裏,如果你使用相同的HashMap的多線程,你一定會得到錯誤(例如,如果在另外一個試圖修改它,你會得到一個併發的修改除了一個線程itterates地圖項),或者如果沒有錯誤,至少一個巨大的性能瓶頸,因爲由於多個線程發送了多個寫入,Map將執行大量重新hashig。另一方面,ConcurrentHashMap非常適合在多個線程之間共享一個實例。您不會得到併發修改異常,並且在多個線程同時寫入時,Map的性能會更好。

+0

換句話說,同步塊不會在引用'_db'上同步,而是在塊嘗試執行時由它引用的對象同步? – riha 2012-08-13 05:51:21

+0

@riha我不知道我明白你的意思。同步BLOCKS使用鎖作爲參數傳遞的實例。同步方法鎖定具有該方法的類的實例(並且相當於「synchronized(this)」塊)。同步靜態方法使用encompasing類作爲鎖 – 2012-08-13 20:22:28

+0

是的,謝謝。之前,我認爲synchronized塊鎖定了傳遞的引用,而不是分配給它的對象實例。這實際上是非常愚蠢的,現在我想到了它...哦,並感謝清理「連接」不是線程安全的。我並沒有真正意識到這一點。 – riha 2012-08-15 06:03:52