おそらくインタビュアーは、アーネスト・フリードマン・ヒルによって提案された解決策に興味を持っていました。ただし、その欠点のため、通常、本番コードでは使用できません。この問題を処理するために次の同期ユーティリティを作成したら、次のようにします。
package com.paypal.risk.ars.dss.framework.concurrent;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class MultiLock <K>{
private ConcurrentHashMap<K, ReentrantLock> locks = new ConcurrentHashMap<K, ReentrantLock>();
/**
* Locks on a given id.
* Make sure to call unlock() afterwards, otherwise serious bugs may occur.
* It is strongly recommended to use try{ }finally{} in order to guarantee this.
* Note that the lock is re-entrant.
* @param id The id to lock on
*/
public void lock(K id) {
while (true) {
ReentrantLock lock = getLockFor(id);
lock.lock();
if (locks.get(id) == lock)
return;
else // means that the lock has been removed from the map by another thread, so it is not safe to
// continue with the one we have, and we must retry.
// without this, another thread may create a new lock for the same id, and then work on it.
lock.unlock();
}
}
/**
* Unlocks on a given id.
* If the lock is not currently held, an exception is thrown.
*
* @param id The id to unlock
* @throws IllegalMonitorStateException in case that the thread doesn't hold the lock
*/
public void unlock(K id) {
ReentrantLock lock = locks.get(id);
if (lock == null || !lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException("Lock for " + id + " is not owned by the current thread!");
locks.remove(id);
lock.unlock();
}
private ReentrantLock getLockFor(K id) {
ReentrantLock lock = locks.get(id);
if (lock == null) {
lock = new ReentrantLock();
ReentrantLock prevLock = locks.putIfAbsent(id, lock);
if (prevLock != null)
lock = prevLock;
}
return lock;
}
}
単純なマップとグローバルロックを使用して、より単純な方法で実装できることに注意してください。ただし、スループットを向上させるために、グローバルロックを回避したかったのです。