2011-02-02 93 views
9

我想問什麼可能是多線程Java應用程序的最佳解決方案,以確保所有線程同步訪問數據庫。例如,每個線程表示單獨的事務,並首先檢查數據庫的值,然後根據答案不得不插入或更新數據庫中的一些字段(注意檢查,插入和提交應用程序之間進行其他處理)。但問題是另一個線程可能在同一個表上做同樣的事情。更具體的例子。線程T1啓動事務,然後檢查表ENTITY_TABLE,如果發現更新日期,則代碼爲'111',如果未找到則插入新條目,然後提交事務。現在想象一下T2線程完全一樣。現在有幾個問題: 1. T1和T2檢查數據庫並且什麼都沒找到,並且都插入相同的條目。 2. T1檢查數據庫,找到具有舊日期的條目,但在提交T2時已經更新條目到更近的日期。 3.如果我們使用緩存並同步對緩存的訪問,我們有一個問題:T1獲取鎖定檢查數據庫和緩存(如果未找到)添加到緩存,釋放鎖定,提交。 T2也是這樣,在緩存中找到要提交的條目。但是T1交易失敗並且被回滾。現在T2狀態不佳,因爲它應該插入到ENTITY_TABLE,但不知道。 4.更多?Java多線程數據庫訪問

我正在創建簡單的自定義緩存與syncronization和解決問題3.但我感興趣也許有一些更簡單的解決方案?有沒有人需要解決類似的問題?你是怎麼做到的?

回答

7

這應該主要在數據庫中處理,方法是配置所需的transaction isolation級別。然後在此之上,你需要選擇你的鎖定策略(optimistic或悲觀)。

如果沒有事務隔離,您將很難確保僅在Java域中保證事務完整性。特別是考慮到即使數據庫目前只能從您的Java應用程序訪問,這在未來可能會發生變化。

現在至於要選擇哪個隔離級別,從描述中可能看起來您需要最高的隔離級別,可序列化的。然而,在實踐中,由於大量鎖定,這往往是一個真正的性能豬。因此您可能需要重新評估您的要求,以針對您的具體情況找到隔離和性能的最佳平衡。

+0

感謝答案。事情是應用程序必須非常高效,它可能運行在16個甚至32個線程上。並且每個都啓動事務,進行大量處理,然後獲取結果並在提交步驟中執行插入和更新 - 全部以jdbc批處理(用於性能)。然後交易被提交。無論如何,在我的情況下,可串行化的級別將會過多,特別是對於32個線程。我希望我能夠通過Java應用程序中的緩存來解決這個問題。 – nesvarbu 2011-02-02 17:41:52

1

非親愛的,我想你必須在查詢之前鎖定表格。這將強制你的線程順序操作。然後,你的線程應該準備好,因爲他們將不得不等待鎖定,當然,鎖定獲取可能會超時。這可能會在您的應用程序中引入相當多的瓶頸,並且您的線程都必須排隊等待數據庫資源。

1

您面臨的問題是transaction isolation

好像你需要讓每個線程鎖定where子句中涉及的行,這需要序列化隔離。

1

你爲什麼要重新發明輪子?我建議使用OR映射器框架用於事務處理數據庫訪問(例如像Hibernate或Eclipselink這樣的JPA規範實現者)。你也可以添加爲你處理事務的Spring DAO。然後你可以專注於業務邏輯,而不必擔心這種低級別的東西。

2

如果您希望SQL從數據庫中選擇一行,然後再更新同一行,您有兩個選擇作爲Java開發人員。

  1. 選擇具有ROWLOCK,或任何 行鎖的語法是您 特定的數據庫。

  2. 選擇該行,做你的處理, 和你準備 更新之前,再次選擇該行查看 如果有其他線程所做的更改。 如果兩個SELECTS返回相同的值 UPDATE。如果沒有,請拋出 錯誤或再次進行處理。

2

我在使用一個使用Sqllite數據庫的多線程Java程序時遇到了這個問題。它使用文件鎖定,所以我必須確保只有一個線程在同一時間工作。

我基本結束了使用同步。當ConnectionFactory返回一個數據庫連接時,它還會返回一個鎖定對象,在使用連接時應該鎖定該對象。所以,你可以做手工同步鎖,或者創建低於該會爲你的類的子類:

/** 
    * Subclass this class and implement the persistInTransaction method to perform 
    * an update to the database. 
    */ 
public abstract class DBOperationInTransaction { 

    protected Logger logger = Logger.getLogger(DBOperationInTransaction.class.getName()); 

    public DBOperationInTransaction(ConnectionFactory connectionFactory) { 
     DBConnection con = null; 

     try { 
      con = connectionFactory.getConnection(); 

      if(con == null) { 
       logger.log(Level.SEVERE, "Could not get db connection"); 
       throw new RuntimException("Could not get db connection"); 
      } 

      synchronized (con.activityLock) { 
       con.connection.setAutoCommit(false); 
       persistInTransaction(con.connection); 
       con.connection.commit(); 
      } 

     } catch (Exception e) { 
      logger.log(Level.SEVERE, "Failed to persist data:", e); 
      throw new RuntimeException(e); 
     } finally { 
      if(con != null) { 
       //Close con.connection silently. 
      } 
     } 
    } 

    /** 
     * Method for persisting data within a transaction. If any SQLExceptions 
     * occur they are logged and the transaction is rolled back. 
     * 
     * In the scope of the method there is a logger object available that any 
     * errors/warnings besides sqlException that you want to log. 
     * 
     * @param con 
     *   Connection ready for use, do not do any transaction handling 
     *   on this object. 
     * @throws SQLException 
     *    Any SQL exception that your code might throw. These errors 
     *    are logged. Any exception will rollback the transaction. 
     * 
     */ 
    abstract protected void persistInTransaction(Connection con) throws SQLException; 

} 

而DBConnection的結構:

final public class DBConnection { 
    public final Connection connection; 
    public final String activityLock; 

    public DBConnection(Connection connection, String activityLock) { 
     this.connection = connection; 
     this.activityLock = activityLock; 
    } 

}