0

有许多线程在同一个地图上的元素上工作,每个线程都有一个代码,例如:

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 中插入一个假元素,然后在该元素上使用“同步”子句强制同步。你有什么想法?(显然,如果线程删除了带有相关代码的元素,那么假元素也会保留在地图中)......

4

3 回答 3

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();              
    }
}

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

于 2012-11-02T23:19:39.823 回答
1

我们使用一种我们称之为 LockMap 的东西。

LockMap 本质上是:

Map<Object, ReadWriteLock>

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

由于 Maps 依赖于等价性,而不是身份,因此两个对象equal()给你相同的锁。

所以:

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

lock1 == lock2 = true

这很方便。

获得锁后,您可以根据需要将其锁定,以控制对对象的访问。

我们做的另一件事是使用 LRU 映射(使用 LinkedHashMap - 见此,以便旧对象键在未使用时可能会从末端脱落。

于 2012-11-02T21:21:55.213 回答
0

你应该使用ConcurrentHashMap

支持检索的完全并发和可调整的预期更新并发的哈希表。

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

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

于 2012-11-02T18:43:42.923 回答