Java中有没有办法使用两个锁对象进行同步?

Jef*_*erg 3 java multithreading synchronized

我想知道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.
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以创建一个由 DatabaseIdentifier 和 ActivityIdentifier 键入键控的锁对象的哈希图,但是当我需要以线程安全的方式创建/访问这些锁时,我将遇到相同的同步问题。

现在我只是在 DatabaseIdentifier 上进行同步。对于一个 DBIdentifier 同时进行多个活动的可能性要小得多,因此我很少会过度锁定。(但不能对相反的方向说同样的话。)

任何人都有一个好的方法来处理这个问题,而不涉及强制不必要的线程等待?

谢谢!

rat*_*eak 5

每个人都DatabaseIdentifier保留一套锁,钥匙上有ActivityIdentifier它所拥有的 s

所以你可以打电话

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

那么你只需要在底层集合上有一个(短)锁(使用 ConcurrentHashMap)dbID

换句话说

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;
}
Run Code Online (Sandbox Code Playgroud)

这比锁定 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);
}
Run Code Online (Sandbox Code Playgroud)

  • “ActivityIdentifier”实际上可以是一个枚举吗?如果是这样,您可以在“DatabaseIdentifier”内使用(预先填充的)“EnumMap”,它与地图一样快速和紧凑。 (2认同)