Java:锁定函数,它们在同一个元素上运行

时间:2012-11-02 18:42:32

标签: java synchronization locking synchronized

有许多线程可以处理同一个地图上的元素,每个线程都有一个代码,例如:

THREAD_1 works on an element with code "1234"
THREAD_2 works on an element with code "1234"
THREAD_3 works on an element with code "9876"
etc...

元素不是永久性的,即THREAD_1可能会删除元素“1234”,然后再次插入。我想要的是,虽然THREAD_1正在处理元素“1234”(同时删除它),但THREAD_2必须等待。

有办法吗?

一种可能的解决方案可能是在HashMap中插入假元素,然后使用该元素上的“synchronized”子句强制执行同步。你有什么想法? (显然,如果一个线程删除了带有相关代码的元素,假元素也会保留在地图中)...

4 个答案:

答案 0 :(得分:2)

鉴于您的特定问题,Java标准对象都无法解决您的所有问题。这是一个我认为正确的解决方案,并且不会在锁定映射中保留任何不必要的键或值:

// we don't use a ConcurrentHashMap, because we have some other operations 
// that need to be performed in atomically with map.put and map.remove.
// ConcurrentHashMap would of course also work, but it doesn't remove the 
// need for external synchronization in in our case.
Map<String, CountingLock> locksMap = new HashMap<String, CountingLock>();
...

HttpResponse myFunction(String key) {

    CountingLock lock;
    synchronized(locksMap){
        lock = locksMap.get(key);
        if(lock == null){
            lock = new CountingLock();
            locksMap.put(key, lock);
        }
        lock.prepare(); // has to be done while holding the lock of locksMap.
                        // basically tells other threads that the current 
                        // thread intends to acquire the lock soon. This way,
                        // the other threads know not to remove this lock 
                        // from locksMap as long as another one has indicated
                        // that he is going to need it soon.
    }

    lock.lock(); // has to be done while NOT holding the lock of locksMap,
                 // or we risk deadlock situations.

    try {
        // ...
        // work
        // ...
    } finally {
        synchronized(locksMap) {
            if(lock.unlock() == 0){
                // no other thread is intending to use this lock any more. 
                // It is safe to remove it from the map. The next thread 
                // will just have to recreate a new lock for the same key.
                locksMap.remove(key);
            }
        }
    }

    return SOMETHING;    
}

private static class CountingLock {
    // The number of threads that are trying to access the protected Key
    private AtomicInteger interestedThreads = new AtomicInteger(0);

    private Lock lock = new ReentrantLock();

    public void prepare(){
        interestedThreads.incrementAndGet();
    }

    public void lock(){
        lock.lock();
    }

    public int unlock(){
        lock.unlock();
        return interestedThreads.decrementAndGet();              
    }
}

此代码应在所有情况下按预期工作。这是一个有趣的问题: - )

答案 1 :(得分:1)

我们使用的东西叫做LockMap。

LockMap本质上是:

Map<Object, ReadWriteLock>

我们有一个同步方法来获取特定对象的锁。

由于地图依赖于等价而不是同一性,因此equal()的两个对象会给你相同的锁定。

所以:

lock1 = map.get(new Thing(1));
lock2 = map.get(new Thing(1));

lock1 == lock2 = true

哪个方便。

获得锁定后,您可以锁定它,以便控制对对象的访问。

我们要做的另一件事是使用LRU映射(使用LinkedHashMap - 请参阅this),以便旧对象密钥在未使用时可以脱离结束。

答案 2 :(得分:0)

您应该使用ConcurrentHashMap

  

一个哈希表,支持检索的完全并发和可更新的预期并发性。

检索操作(包括get)通常不会阻止,因此可能与更新操作重叠(包括put和remove)。

  

更新操作之间允许的并发性由可选的concurrencyLevel构造函数参数(缺省值16)引导,该参数用作内部大小调整的提示。该表在内部进行分区,以尝试允许指定数量的并发更新而不会发生争用。因为散列表中的放置基本上是随机的,所以实际的并发性会有所不同

答案 3 :(得分:0)

关于名称/键的锁定有一个类似的问题,并讨论了这个主题:Simple Java name based locks?