簡體   English   中英

有沒有辦法在Java中使用兩個鎖定對象進行同步?

[英]Is there a way to synchronize using two lock objects in Java?

我想知道Java中是否有一種方法可以使用兩個鎖定對象進行同步。 我的意思不是鎖定任何物體上時,我的意思是只有兩個鎖定。

例如,如果我有4個線程:

  • 線程A使用Object1和Object2請求鎖定
  • 線程B使用Object1和Object3請求鎖定
  • 線程C使用Object4和Object2請求鎖定
  • 線程D使用Object1和Object2請求鎖定

在上面的場景中,線程A和線程D將共享一個鎖,但線程B和線程C將擁有自己的鎖。 即使它們與兩個對象中的一個重疊,但只有在兩者都重疊時才會應用相同的鎖。

所以我有一個由許多線程調用的方法,它將根據特定的數據庫執行特定的活動類型。 我有數據庫和活動的標識符對象,我可以保證該操作是線程安全的,只要它不是基於與另一個線程相同的數據庫的相同活動。

我理想的代碼看起來像:

public void doActivity(DatabaseIdentifier dbID, ActivityIdentifier actID) {    
    synchronized( dbID, actID ) { // <--- Not real Java
       // Do an action that can be guaranteed thread-safe per unique
       // combination of dbIT and actID, but needs to share a 
       // lock if they are both the same.
    }
}

我可以創建一個由DatabaseIdentifier和ActivityIdentifier鍵入的鎖對象的哈希映射,但是當我需要以線程安全的方式創建/訪問這些鎖時,我將遇到同樣的同步問題。

現在我只是在DatabaseIdentifier上進行同步。 對於一個DBIdentifier,同時進行多個活動的可能性要小得多,因此我很少會過度鎖定。 (但不能說相反的方向相同。)

任何人都有一個很好的方法來處理這個不涉及強迫不必要的線程等待?

謝謝!

讓每個DatabaseIdentifier保留一組鎖定到它擁有的ActivityIdentifier的鎖

所以你可以打電話

public void doActivity(DatabaseIdentifier dbID, ActivityIdentifier actID) {    
    synchronized( dbID.getLock(actID) ) { 
       // Do an action that can be guaranteed thread-safe per unique
       // combination of dbIT and actID, but needs to share a 
       // lock if they are both the same.
    }
}

那么你只需要在dbID對底層集合(使用ConcurrentHashMap)進行(短)鎖定

換一種說法

ConcurrentHashMap<ActivityIdentifier ,Object> locks = new...
public Object getLock(ActivityIdentifier actID){
    Object res = locks.get(actID); //avoid unnecessary allocations of Object

    if(res==null) {
        Object newLock = new Object();
        res = locks.puIfAbsent(actID,newLock );
        return res!=null?res:newLock;
    } else return res;
}

這比在dbID上鎖定完整操作要好(特別是在長時間操作時),但仍比理想情況更差

更新以響應有關EnumMap的評論

private final EnumMap<ActivityIdentifier ,Object> locks;

/**
  initializer ensuring all values are initialized 
*/
{
    EnumMap<ActivityIdentifier ,Object> tmp = new EnumMap<ActivityIdentifier ,Object>(ActivityIdentifier.class)
    for(ActivityIdentifier e;ActivityIdentifier.values()){
        tmp.put(e,new Object());
    }
    locks = Collections.unmodifiableMap(tmp);//read-only view ensures no modifications will happen after it is initialized making this thread-safe
}


public Object getLock(ActivityIdentifier actID){
    return locks.get(actID);
}

我認為你應該采用hashmap的方式,但將其封裝在flyweight工廠中。 即,你打電話:

FlyweightAllObjectsLock lockObj = FlyweightAllObjectsLock.newInstance(dbID, actID);

然后鎖定該對象。 flyweight工廠可以在地圖上獲取讀鎖定,以查看該鍵是否在那里,如果不是,則只執行寫鎖定。 它應該減少並發因素。

您可能還希望在該映射上使用弱引用,以避免將內存保留在垃圾回收中。

我想不出這樣做的方法真正抓住你鎖定一對物體的想法。 一些低級並發boffin可能能夠發明一個,但我懷疑我們是否有必要的原語來在Java中實現它。

我認為使用對作為鍵來識別鎖定對象的想法是一個很好的想法。 如果要避免鎖定,請排列查找,使其不執行任何操作。

我會建議一個兩級地圖,模糊地說:

Map<DatabaseIdentifier, Map<ActivityIdentifier, Lock>> locks;

因此含糊地使用:

synchronized (locks.get(databaseIdentifier).get(activityIdentifier)) {
    performSpecificActivityOnDatabase();
}

如果您知道所有數據庫和活動是預先確定的,那么只需在應用程序啟動時創建一個包含所有組合的完美法線貼圖,並完全按照上面的方式使用它。 唯一的鎖定在鎖定對象上,並且沒有爭用。

如果您不知道數據庫和活動將是什么,或者有太多組合來預先創建完整的地圖,那么您將需要以遞增方式創建地圖。 這就是Concurrency Fun Times的開始。

直接的解決方案是懶惰地創建內部映射和鎖,並使用普通鎖保護這些操作:

Map<ActivityIdentifier, Object> locksForDatabase;
synchronized (locks) {
    locksForDatabase = locks.get(databaseIdentifier);
    if (locksForDatabase == null) {
        locksForDatabase = new HashMap<ActivityIdentifier, Object>();
        locks.put(databaseIdentifier, locksForDatabase);
    }
}
Object lock;
synchronized (locksForDatabase) {
    lock = locksForDatabase.get(locksForDatabase);
    if (lock == null) {
        lock = new Object();
        locksForDatabase.put(locksForDatabase, lock);
    }
}
synchronized (lock) {
    performSpecificActivityOnDatabase();
}

正如您明顯意識到的那樣,這將引發太多爭用。 我只提到說教的完整性。

您可以通過使外部地圖並發來改進它:

ConcurrentMap<DatabaseIdentifier, Map<ActivityIdentifier, Object>> locks;

和:

Map<ActivityIdentifier, Object> newHashMap = new HashMap<ActivityIdentifier, Object>();
Map<ActivityIdentifier, Object> locksForDatabase = locks.putIfAbsent(databaseIdentifier, newHashMap);
if (locksForDatabase == null) locksForDatabase = newHashMap;
Object lock;
synchronized (locksForDatabase) {
    lock = locksForDatabase.get(locksForDatabase);
    if (lock == null) {
        lock = new Object();
        locksForDatabase.put(locksForDatabase, lock);
    }
}
synchronized (lock) {
    performSpecificActivityOnDatabase();
}

你的唯一鎖爭用將出現在每個數據庫的地圖上,在put和get的持續時間內,並且根據你的報告,將沒有那么多。 您可以將內部地圖轉換為ConcurrentMap以避免這種情況,但這聽起來有點過分。

但是,將會創建一個穩定的HashMap實例流,以便將其提供給putIfAbsent然后被丟棄。 你可以通過一種雙重檢查鎖定的后現代原子混合來避免這種情況; 用以下內容替換前三行:

Map<ActivityIdentifier, Object> locksForDatabase = locks.get(databaseIdentifier);
if (locksForDatabase == null) {
    Map<ActivityIdentifier, Object> newHashMap = new HashMap<ActivityIdentifier, Object>();
    locksForDatabase = locks.putIfAbsent(databaseIdentifier, newHashMap);
    if (locksForDatabase == null) locksForDatabase = newHashMap;
}

在每個數據庫映射已經存在的常見情況下,這將執行單個並發get 在不常見的情況下,它會做一個額外但必要的new HashMap()putIfAbsent 在非常罕見的情況下,它沒有,但另一個線程也發現,其中一個線程將執行冗余的new HashMap()putIfAbsent 這不應該是昂貴的。

實際上,我發現這是一個非常糟糕的想法,你應該只將兩個標識符放在一起制作一個雙倍大小的鍵,並使用它來在一個ConcurrentHashMap進行查找。 可悲的是,我太懶了,也無法刪除上面的內容。 考慮這個建議是迄今為止閱讀的特別獎。

PS看到一個Object的實例只是一個鎖,它總是讓我感到懊惱。 我建議稱他們為LockGuffins

你的hashmap建議就是我過去所做的。 我所做的唯一改變是使用ConcurrentHashMap來最小化同步。

另一個問題是如果可能的鍵將要改變,如何清理地圖。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM