ObjectLock.java
/*
* ObjectLock
*
* $Id$
* $URL$
*/
package gov.usgs.util;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Reentrant ReadWrite Locking per object.
*
* This is intended for use when multiple sections of code should allow
* concurrent access, but only when operating on independent objects.
*
* @param <T>
* The type of object used for locking. This object is used as a key
* in a HashMap. Objects that are equal, but not necessarily ==,
* reference the same lock.
*/
public class ObjectLock<T> {
/** map object to corresponding lock for object. */
private HashMap<T, ReentrantReadWriteLock> locks = new HashMap<T, ReentrantReadWriteLock>();
/**
* keep track of how many threads have, or are about to, use the lock object
* in the map 'locks'. When this count reaches zero, it is safe to remove
* the lock object.
*/
private HashMap<T, Integer> lockThreadCounts = new HashMap<T, Integer>();
private final Object syncObject = new Object();
/**
* Construct a new ObjectLock object.
*/
public ObjectLock() {
}
/**
* Get the lock for an object.
*
* This method must only be called by one thread at a time. synchronization
* is handled by other methods in this class.
*
* @param object
* object to lock.
* @return lock corresponding to object.
*/
private ReentrantReadWriteLock getLock(final T object) {
ReentrantReadWriteLock lock = locks.get(object);
if (lock == null) {
lock = new ReentrantReadWriteLock(true);
locks.put(object, lock);
}
return lock;
}
/**
* Increment the thread count for an object.
*
* This method must only be called by one thread at a time. synchronization
* is handled by other methods in this class.
*
* @param object
*/
private void incrementThreadCount(final T object) {
Integer threadCount = lockThreadCounts.get(object);
if (threadCount == null) {
threadCount = Integer.valueOf(0);
}
threadCount = threadCount + 1;
lockThreadCounts.put(object, threadCount);
}
/**
* Decrement the thread count for an object. Also, when the thread count
* reaches zero, the lock corresponding to this object is removed from the
* locks map.
*
* This method must only be called by one thread at a time. synchronization
* is handled by other methods in this class.
*
* @param object
*/
private void decrementThreadCount(final T object) {
Integer threadCount = lockThreadCounts.get(object);
if (threadCount == null) {
throw new IllegalStateException(
"Trying to decrement thread count that does not exist.");
}
threadCount = threadCount - 1;
lockThreadCounts.put(object, threadCount);
if (threadCount == 0) {
// no threads are using this lock anymore, cleanup
locks.remove(object);
lockThreadCounts.remove(object);
}
}
/**
* Acquire a read lock for an object.
*
* Callers MUST subsequently call releaseReadLock.
*
* @param object
* the object to lock for reading.
* @throws InterruptedException if thread is interrupted
*/
public void acquireReadLock(final T object) throws InterruptedException {
ReentrantReadWriteLock lock = null;
synchronized (syncObject) {
lock = getLock(object);
incrementThreadCount(object);
}
// do this outside synchronized for concurrency
lock.readLock().lockInterruptibly();
}
/**
* Check if the calling thread currently has a write lock for the object.
*
* @param object
* object to check.
* @return true if the current thread currently holds a write lock, false
* otherwise.
*/
public boolean haveWriteLock(final T object) {
ReentrantReadWriteLock lock = locks.get(object);
if (lock == null) {
return false;
}
return lock.isWriteLockedByCurrentThread();
}
/**
* Release a held read lock for an object.
*
* Callers MUST have previously called acquireReadLock(object).
*
* @param object
* the object to unlock for reading.
*/
public void releaseReadLock(final T object) {
synchronized (syncObject) {
ReentrantReadWriteLock lock = getLock(object);
decrementThreadCount(object);
lock.readLock().unlock();
}
}
/**
* Acquire a write lock for an object.
*
* Callers MUST also call releaseWriteLock(object).
*
* @param object
* the object to lock for writing.
* @throws InterruptedException if thread is interrupted
*/
public void acquireWriteLock(final T object) throws InterruptedException {
ReentrantReadWriteLock lock = null;
synchronized (syncObject) {
lock = getLock(object);
incrementThreadCount(object);
}
// do this outside synchronized for concurrency
lock.writeLock().lockInterruptibly();
}
/**
* Release a held write lock for an object.
*
* Callers MUST have previously called acquireWriteLock(object).
*
* @param object
* the object to unlock for writing.
*/
public void releaseWriteLock(final T object) {
synchronized (syncObject) {
ReentrantReadWriteLock lock = getLock(object);
decrementThreadCount(object);
lock.writeLock().unlock();
}
}
/**
* This is a synonym for acquireWriteLock, which is an exclusive lock for
* this object.
*
* @param object
* the object to lock.
* @throws InterruptedException if thread is interrupted
*/
public void acquireLock(final T object) throws InterruptedException {
acquireWriteLock(object);
}
/**
* This is a synonym for releaseWriteLock, which is an exclusive lock for
* this object.
*
* @param object
* the object to unlock.
*/
public void releaseLock(final T object) {
releaseWriteLock(object);
}
}