/******************************************************************************
* Copyright © 2013-2016 The Nxt Core Developers. *
* *
* See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* Nxt software, including this file, may be copied, modified, propagated, *
* or distributed except according to the terms contained in the LICENSE.txt *
* file. *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
package nxt.util;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* <p>
* A read or update lock allows shared access while a write lock enforces exclusive access. Multiple
* threads can hold the read lock but only one thread can hold the update or write lock. If a thread
* obtains a lock that it already holds, it must release the lock the same number of times that it
* obtained the lock.
* </p>
* <ul>
* <li>An attempt to obtain the read lock while another thread holds the write lock will cause
* the thread to be suspended until the write lock is released.</li>
* <li>An attempt to obtain the update lock while another thread holds the update or write lock
* will cause the thread to be suspended until the blocking lock is released. A thread
* holding the update lock can subsequently obtain the write lock to gain exclusive access.
* An attempt to obtain the update lock while holding either the read lock or the write lock
* will result in an exception.</li>
* <li>An attempt to obtain the write lock while another thread holds the read, update or write lock
* will cause the thread to be suspended until the blocking lock is released.
* An attempt to obtain the write lock while holding the read lock will result in an exception.</li>
* </ul>
*/
public class ReadWriteUpdateLock {
/** Lock shared by the read and write locks */
private final ReentrantReadWriteLock sharedLock = new ReentrantReadWriteLock();
/** Lock used by the update lock */
private final ReentrantLock mutexLock = new ReentrantLock();
/** Lock counts */
private final ThreadLocal<LockCount> lockCount = ThreadLocal.withInitial(LockCount::new);
/** Read lock */
private final ReadLock readLock = new ReadLock();
/** Update lock */
private final UpdateLock updateLock = new UpdateLock();
/** Write lock */
private final WriteLock writeLock = new WriteLock();
/**
* Return the read lock
*
* @return Read lock
*/
public Lock readLock() {
return readLock;
}
/**
* Return the update lock
*
* @return Update lock
*/
public Lock updateLock() {
return updateLock;
}
/**
* Return the write lock
*
* @return Write lock
*/
public Lock writeLock() {
return writeLock;
}
/**
* Lock interface
*/
public interface Lock {
/**
* Obtain the lock
*/
void lock();
/**
* Release the lock
*/
void unlock();
/**
* Check if the thread holds the lock
*
* @return TRUE if the thread holds the lock
*/
boolean hasLock();
}
/**
* Read lock
*/
private class ReadLock implements Lock {
/**
* Obtain the lock
*/
@Override
public void lock() {
sharedLock.readLock().lock();
lockCount.get().readCount++;
}
/**
* Release the lock
*/
@Override
public void unlock() {
sharedLock.readLock().unlock();
lockCount.get().readCount--;
}
/**
* Check if the thread holds the lock
*
* @return TRUE if the thread holds the lock
*/
@Override
public boolean hasLock() {
return lockCount.get().readCount != 0;
}
}
/**
* Update lock
*/
private class UpdateLock implements Lock {
/**
* Obtain the lock
*
* Caller must not hold the read or write lock
*/
@Override
public void lock() {
LockCount counts = lockCount.get();
if (counts.readCount != 0) {
throw new IllegalStateException("Update lock cannot be obtained while holding the read lock");
}
if (counts.writeCount != 0) {
throw new IllegalStateException("Update lock cannot be obtained while holding the write lock");
}
mutexLock.lock();
counts.updateCount++;
}
/**
* Release the lock
*/
@Override
public void unlock() {
mutexLock.unlock();
lockCount.get().updateCount--;
}
/**
* Check if the thread holds the lock
*
* @return TRUE if the thread holds the lock
*/
@Override
public boolean hasLock() {
return lockCount.get().updateCount != 0;
}
}
/**
* Write lock
*/
private class WriteLock implements Lock {
/**
* Obtain the lock
*
* Caller must not hold the read lock
*/
@Override
public void lock() {
LockCount counts = lockCount.get();
if (counts.readCount != 0) {
throw new IllegalStateException("Write lock cannot be obtained while holding the read lock");
}
boolean lockObtained = false;
try {
mutexLock.lock();
counts.updateCount++;
lockObtained = true;
sharedLock.writeLock().lock();
counts.writeCount++;
} catch (Exception exc) {
if (lockObtained) {
mutexLock.unlock();
counts.updateCount--;
}
throw exc;
}
}
/**
* Release the lock
*/
@Override
public void unlock() {
LockCount counts = lockCount.get();
sharedLock.writeLock().unlock();
counts.writeCount--;
mutexLock.unlock();
counts.updateCount--;
}
/**
* Check if the thread holds the lock
*
* @return TRUE if the thread holds the lock
*/
@Override
public boolean hasLock() {
return lockCount.get().writeCount != 0;
}
}
/**
* Lock counts
*/
private class LockCount {
/** Read lock count */
private int readCount;
/** Update lock count */
private int updateCount;
/** Write lock count */
private int writeCount;
}
}