2011-07-07 138 views
3

我有一個Web應用程序,我使用的Oracle數據庫和我基本上是這樣的方法上:Java線程鎖定特定對象

public static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) { 
     if (!methodThatChecksThatObjectAlreadyExists) { 
     storemyObject() //pseudo code 
    } 
    // Have to do a lot other saving stuff, because it either saves everything or nothing 
    commit() // pseudo code to actually commit all my changes to the database. 
} 

現在沒有任何一種這樣的同步N個線程當然這個方法可以自由地訪問,當兩個線程進入這個方法時,問題就出現了,當然這個方法沒有任何東西,然後他們都可以提交事務,創建一個重複的對象。

我不想用我的數據庫中唯一的密鑰標識符來解決這個問題,因爲我不認爲我應該抓住那個SQLException

我也不能在提交前檢查,因爲有幾個檢查不僅1,這將花費相當多的時間。

我對鎖和線程的使用經驗有限,但我的想法基本上是將此代碼鎖定在它正在接收的對象上。我不知道是否例如說我收到一個Integer對象,並且我使用值1鎖定Integer,是否僅阻止其他具有值1的Integer的線程進入,並且所有其他具有value != 1的線程都可以自由輸入? ,這是如何工作的?

此外,如果這是它的工作原理,鎖定對象是如何比較的?它如何確定它們實際上是同一個對象?關於這一點的好文章也將不勝感激。

你會如何解決這個問題?

+1

爲什麼你不覺得你應該捕捉SQL異常? Oracle內置了幾十年來由非常聰明的人開發的功能,每天都有數以萬計的已安裝用戶使用,可以解決您的問題。他們的輪子工作正常。只要你的應用程序變得足夠大,需要進行集羣,你就會討厭這種內存鎖定。 – Affe

+0

@Affe該應用程序實際上已經在tomcat集羣中,所以是的,我擔心線程鎖定的原因...我想捕捉SQL異常會工作,我只是想看到其他的可能性,捕捉的東西SQL例外是,我將不得不檢查它是正確的ORA號碼,如果有的話,這是否經常改變? –

回答

6

你的想法是一個很好的想法。這是簡單/天真的版本,但它不太可能工作:

public static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) { 
    synchronized (theObjectIwantToSave) { 
     if (!methodThatChecksThatObjectAlreadyExists) { 
      storemyObject() //pseudo code 
     } 
     // Have to do a lot other saving stuff, because it either saves everything or nothing 
     commit() // pseudo code to actually commit all my changes to the database. 
    } 
} 

此代碼使用對象本身作爲鎖。但它必須是相同的對象(即objectInThreadA == objectInThreadB)如果它的工作。如果兩個線程的對象,它是一個複製對方對操作 - 即具有相同的「ID」,例如,那麼你就需要要麼同步整個方法:

public static synchronized void saveSomethingImportantToDataBase(Object theObjectIwantToSave) ... 

的這將當然大大降低了併發性(吞吐量將一次下降到一個線程使用該方法 - 要避免)。

或者找到一種方式來獲得基於保存對象的鎖定的對象,這樣的做法:

private static final ConcurrentHashMap<Object, Object> LOCKS = new ConcurrentHashMap<Object, Object>(); 
public static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) { 
    synchronized (LOCKS.putIfAbsent(theObjectIwantToSave.getId(), new Object())) { 
     ....  
    } 
    LOCKS.remove(theObjectIwantToSave.getId()); // Clean up lock object to stop memory leak 
} 

這最後一個版本,它的建議之一:它將確保兩個保存共享對象同一個「id」被同一個鎖對象鎖定 - 方法ConcurrentHashMap.putIfAbsent()是線程安全的,所以「這將工作」,它只需要objectInThreadA.getId().equals(objectInThreadB.getId())正常工作。此外,由於java的autoboxing,getId()的數據類型可以是任何東西,包括基元(例如int)。

如果覆蓋equals()hashcode()你的對象,那麼你可以使用對象本身,而不是object.getId(),這將是一個進步(感謝@TheCapn指出這一點)

該解決方案將只與工作在一個JVM中。如果你的服務器是集羣化的,那麼一個完全不同的球賽和java的鎖定機制不會對你有所幫助。您將不得不使用羣集鎖定解決方案,這超出了本答案的範圍。

+0

java如何確定對象實際上是同一個對象? –

+0

@OscarMk - 你的意思是Java如何知道這兩個線程正在引用同一個對象?在這種情況下,它在內存中的身份足以知道它正在訪問什麼對象。爲了更全面地瞭解您的問題,請查閱「讀者/作者問題」,這是一個流行的併發問題。 – Grambot

+0

@TheCapn好的,我會研究這個問題,所以它基本上決定與對象1 == object2 ??相等,是否有可能做一個object1.equals(object2)?,它們實際上不會引用到內存中的相同對象,但它們具有相同的屬性。我想鎖定具有相同屬性的對象,而不是在內存中的相同位置。 –

0
public static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) { 
    synchronized (theObjectIwantToSave) { 

     if (!methodThatChecksThatObjectAlreadyExists) { 
     storemyObject() //pseudo code 
     } 
// Have to do a lot other saving stuff, because it either saves everything or nothing 
     commit() // pseudo code to actually commit all my changes to the database. 
    } 
} 

synchronized關鍵字鎖定所需的對象,以便其他方法無法訪問它。

0

我不認爲你有什麼選擇,只能採取你似乎不想做的解決方案之一。

就你而言,我認爲objectYouWantToSave上的任何類型的同步都不會起作用,因爲它們基於Web請求。因此,每個請求(在它自己的線程上)最有可能擁有它自己的對象實例。即使它們可能被認爲是邏輯上相同的,但這對同步並不重要。

0

同步關鍵字(或其他同步操作)必須但不足以解決您的問題。您應該使用數據結構來存儲使用哪個整數值。在我們的例子中使用HashSet。不要忘記哈希集合中乾淨的過時記錄。

private static HashSet <Integer>isUsed= new HashSet <Integer>(); 

public synchronized static void saveSomethingImportantToDataBase(Object theObjectIwantToSave) { 

     if(isUsed.contains(theObjectIwantToSave.your_integer_value) != null) { 

     if (!methodThatChecksThatObjectAlreadyExists) { 
     storemyObject() //pseudo code 
     } 
// Have to do a lot other saving stuff, because it either saves everything or nothing 
     commit() // pseudo code to actually commit all my changes to the database. 
     isUsed.add(theObjectIwantToSave.your_integer_value); 

    } 
} 
0

爲了回答您的有關鎖定整型問題,簡單的答案是否定的 - 它不會阻止線程與進入相同的值另一個整數實例。長的答案:取決於你如何獲得整數 - 通過構造函數,通過重用一些實例或通過valueOf(使用一些緩存)。無論如何,我不會依賴它。

一個可行的解決方案,將工作就是讓方法同步:

public static synchronized void saveSomethingImportantToDataBase(Object theObjectIwantToSave) { 
    if (!methodThatChecksThatObjectAlreadyExists) { 
     storemyObject() //pseudo code 
    } 
    // Have to do a lot other saving stuff, because it either saves everything or nothing 
    commit() // pseudo code to actually commit all my changes to the database. 
} 

這可能不是最好的解決方案的性能,明智的,但它是保證工作(注意,如果你不是在集羣環境),直到找到更好的解決方案。

1

我的意見是你沒有掙扎與一個真正的線程問題。

您最好讓DBMS自動分配非衝突的行ID。

如果您需要使用現有的行ID將它們存儲爲線程局部變量。 如果不需要共享數據,則不要在線程之間共享數據。

http://download.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html

Oracle DBMS中是在保持數據一致好得多當應用程序服務器或Web容器。

「當插入一行時,許多數據庫系統會自動生成一個唯一的鍵字段Oracle數據庫通過序列和觸發器提供相同的功能JDBC 3.0引入了自動生成的鍵功能的檢索,使您能夠檢索這樣產生的值。在JDBC 3.0,下面的界面增強,可以支持自動生成鍵功能的恢復......」

http://download.oracle.com/docs/cd/B19306_01/java.102/b14355/jdbcvers.htm#CHDEGDHJ

+0

問題是直到commit()纔會插入任何內容。 –

+0

我想你應該能夠在最終提交事務之前檢索密鑰。假設pstmt是你準備好的statemt,並且你已經執行了它。 ResultSet generatedKeys = pstmt.getGeneratedKeys(); –

+0

我會研究這個,但看起來不錯+1 –

1

波希米亞的答案似乎有競爭條件的問題,如果一個線程在同步部分,而另一個線程從Map中刪除同步對象,等等。所以這裏是一個替代t帽子利用WeakRef的。

// there is no synchronized weak hash map, apparently 
// and Collections.synchronizedMap has no putIfAbsent method, so we use synchronized(locks) down below 

WeakHashMap<Integer, Object> locks = new WeakHashMap<Object, Object>(); 

public void saveSomethingImportantToDataBase(Object objectToSave) { 
    Object lock; 
    synchronized (locks) { 
    lock = locks.get(objectToSave.getId()); 
    if (lock == null) { 
     lock = new Object(); 
     locks.put(objectToSave.getId(), lock); 
    } 
    } 
    synchronized (lock) { 
    // synchronized work here (synchronized by objectToSave's id) 
    } 
    // no releasing needed, weakref does that for us, we're done! 
} 

以及如何使用上面的風格體系更具體的例子:

static WeakHashMap<Integer, Object> locks = new WeakHashMap<Integer, Object>(); 

static Object getSyncObjectForId(int id) { 
    synchronized (locks) { 
    Object lock = locks.get(id); 
    if (lock == null) { 
     lock = new Object(); 
     locks.put(id, lock); 
    } 
    return lock; 
    } 
} 

然後在其他地方使用它像這樣:

... 
    synchronized (getSyncObjectForId(id)) { 
    // synchronized work here 
    } 
... 

原因這部作品基本上,如果兩個具有匹配鍵的對象進入關鍵塊,第二個將檢索第一個已經使用的鎖(或者是留下的還沒有被GC'ed的那個)。但是,如果它未被使用,兩者都會留下方法並刪除它們對鎖對象的引用,因此它會被安全地收集。

如果你有一個有限的「已知大小」的同步模式集合,你可以避免使用HashMap,而應該使用ConcurrentHashMap,它的putIfAbsent方法可能更容易理解。

3

這是改編自波希米亞的回答And360的評論,試圖避免競爭條件等。雖然我更喜歡的選擇我other answer這個問題在這一塊,略:

import java.util.HashMap; 
import java.util.concurrent.atomic.AtomicInteger; 

// it is no advantage of using ConcurrentHashMap, since we synchronize access to it 
// (we need to in order to "get" the lock and increment/decrement it safely) 
// AtomicInteger is just a mutable int value holder 
// we don't actually need it to be atomic 
static final HashMap<Object, AtomicInteger> locks = new HashMap<Integer, AtomicInteger>(); 

public static void saveSomethingImportantToDataBase(Object objectToSave) { 
    AtomicInteger lock; 
    synchronized (locks) { 
     lock = locks.get(objectToSave.getId()); 
     if (lock == null) { 
      lock = new AtomicInteger(1); 
      locks.put(objectToSave.getId(), lock); 
     } 
     else 
      lock.incrementAndGet(); 
    } 
    try { 
     synchronized (lock) { 
      // do synchronized work here (synchronized by objectToSave's id) 
     } 
    } finally { 
     synchronized (locks) { 
      lock.decrementAndGet(); 
      if (lock.get() == 0) 
       locks.remove(id); 
     } 
    } 
} 

你可以拆分這些進入助手方法「獲取鎖定對象」和「釋放鎖定」,或者清除代碼。這種方式感覺比我的other answer多一點kludgey。

1

如果你能偶爾過同步(即不需要的時候按順序完成的工作)住試試這個:

  1. 與鎖定的對象創建一個表。表格越大,過度同步的可能性越小。
  2. 將一些散列函數應用於您的id來計算表索引。如果你的id是數字的,你可以使用餘數(模)函數,如果它是一個字符串,則使用hashCode()和餘數。
  3. 從表中獲取鎖並在其上進行同步。

的IdLock類:

public class IdLock { 

private Object[] locks = new Object[10000]; 

public IdLock() { 
    for (int i = 0; i < locks.length; i++) { 
    locks[i] = new Object(); 
    } 
} 

public Object getLock(int id) { 
    int index = id % locks.length; 
    return locks[index]; 
} 

}

及其用途:

private idLock = new IdLock(); 

public void saveSomethingImportantToDataBase(Object theObjectIwantToSave) { 
    synchronized (idLock.getLock(theObjectIwantToSave.getId())) { 
    // synchronized work here 
    } 
}