首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Java中有使用两个锁对象进行同步的方法吗?

在Java中有使用两个锁对象进行同步的方法吗?
EN

Stack Overflow用户
提问于 2011-11-02 19:06:56
回答 4查看 2.2K关注 0票数 3

我想知道Java中是否有一种使用两个锁对象进行同步的方法。我不是指锁定任何一个对象,我是说只锁定这两个对象。

例如,如果我有4个线程:

  • 线程A使用Object1和Object2请求锁
  • 线程B使用Object1和Object3请求锁
  • 线程C使用Object4和Object2请求锁
  • 线程D使用Object1和Object2请求锁

在上面的场景中,线程A和线程D将共享一个锁,但是线程B和线程C将拥有自己的锁。即使它们与两个对象中的一个重叠,相同的锁只在两个对象上重叠时才适用。

因此,我有一个由许多线程调用的方法,它将根据特定的数据库执行特定的活动类型。我有数据库和活动的标识符对象,我可以保证操作是线程安全的,只要它不是基于与另一个线程相同的数据库的相同活动。

我的理想代码应该如下所示:

代码语言:javascript
复制
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来说,同时进行多个活动的可能性要小得多,所以我很少会过度锁定。(相反的方向则不能这么说。)

有人有一个很好的方法来处理这个问题,而不是强迫不必要的线程等待?

谢谢!

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-11-02 19:37:14

让每个DatabaseIdentifier保存一组锁,键控到它所拥有的ActivityIdentifier

所以你可以打电话

代码语言:javascript
复制
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.
    }
}

然后,您只需要对基础集合(使用ConcurrentHashMap)在dbID中设置一个(短)锁。

换句话说

代码语言:javascript
复制
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的评论进行更新

代码语言:javascript
复制
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);
}
票数 5
EN

Stack Overflow用户

发布于 2011-11-02 19:27:41

我认为您应该遵循hashmap的方式,但应该将其封装在飞重工厂中。呃,你打电话:

代码语言:javascript
复制
FlyweightAllObjectsLock lockObj = FlyweightAllObjectsLock.newInstance(dbID, actID);

然后锁定那个物体。飞重工厂可以在地图上获得一个读锁,以查看密钥是否在那里,如果不是,则只执行写锁。它应该降低并发系数。

您还可能希望在该映射中使用弱引用,以避免内存从垃圾收集中保存。

票数 2
EN

Stack Overflow用户

发布于 2011-11-02 19:31:26

我想不出有什么方法能真正捕捉到锁定一对对象的想法。一些低级别的并发性boffin可能能够发明一个,但是我怀疑我们是否会有必要的原语来实现它。

我认为使用这些对作为密钥来识别锁对象的想法是一个好主意。如果您想避免锁定,那么就安排查找,这样它就不会执行任何操作。

我建议一张两层的地图,模糊地说:

代码语言:javascript
复制
Map<DatabaseIdentifier, Map<ActivityIdentifier, Lock>> locks;

这样含糊地使用:

代码语言:javascript
复制
synchronized (locks.get(databaseIdentifier).get(activityIdentifier)) {
    performSpecificActivityOnDatabase();
}

如果您知道所有的数据库和活动都是预先的,那么在应用程序启动时创建一个包含所有组合的完全正常的映射,并按照上面的方式使用它。唯一的锁定是锁定对象,没有争用。

如果您不知道数据库和活动是什么,或者有太多的组合来预先创建完整的地图,那么您将需要增量地创建映射。这里是并发乐趣时代的起点。

简单的解决方案是懒洋洋地创建内部映射和锁,并使用普通锁保护这些操作:

代码语言:javascript
复制
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();
}

你显然知道,这会导致太多的争论。我提到它只是为了教学的完整性。

您可以通过使外部映射并行化来改进它:

代码语言:javascript
复制
ConcurrentMap<DatabaseIdentifier, Map<ActivityIdentifier, Object>> locks;

和:

代码语言:javascript
复制
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中,然后被丢弃。您可以使用一种后现代原子混合的双重检查锁定来避免这种情况;将前三行替换为:

代码语言:javascript
复制
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中进行查找。遗憾的是,我太懒,太虚荣,无法删除上面的内容。把这个建议当作阅读这么远的一项特别奖吧。

看到一个对象的实例被用作一个锁,总是会让我有点恼火。我建议叫他们LockGuffins

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7985971

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档