com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock Maven / Gradle / Ivy
/**
* Copyright 2013 Niall Gallagher
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.concurentlocks;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.*;
/**
* An implementation of {@link ReadWriteUpdateLock}, extending the functionality of the JDK
* {@link ReentrantReadWriteLock} with an update lock in addition to the read and write lock, supporting upgrade
* from read-only operation to writing status, and downgrade again.
*
* Background - JDK ReentrantReadWriteLock
* The ReentrantReadWriteLock in the JDK classifies threads as needing to read-without-write,
* write-without-read, or write-before-read. Threads can obtain the read lock, or the write lock, and if
* they have the write lock, they can downgrade it to a read lock. But the key limitation is that it does not support
* read-before-write: threads which hold a read lock cannot upgrade it to a write lock.
*
* Imagine a data structure, let's say a cache, where most requests are to read data, but also there is a maintenance
* thread responsible for periodically traversing the data structure to find and update stale entries. The maintenance
* thread thus would mostly read entries while traversing the data structure, and periodically it might encounter
* entries which need to be updated.
*
* If a classic ReentrantReadWriteLock is used to control access, the write-before-read support afforded by that lock
* would be insufficient, because if at any point the thread updated an entry and then downgraded its write lock to a
* read lock, it would not be able to upgrade it again to a write lock if it found other stale entries. So the
* maintenance thread would need to hold the write lock for its entire traversal of the data structure, preventing read
* access for that entire duration.
*
* ReentrantReadWriteUpdateLock Overview
* The third type of lock provided, an update lock, is like a super read lock. It behaves like a read lock, in that it
* allows read access to the thread which holds it, and it concurrently "plays nice" with other threads which hold
* regular read locks, allowing those threads concurrent read access.
*
* The key difference is that the update lock can be upgraded from its read-only status, to a write lock. Thus it
* supports read-before-write usage. Also the write lock can be downgraded again to an update lock, write-before-read
* usage. A restriction however is that, similar to the write lock, only one thread may acquire the update lock at a
* time.
*
* This is sufficient in situations like the example above, to allow read-mostly, write-occasionally threads to operate
* on the data structure without blocking access to read-only threads most of the time: upgrading to the write lock only
* for the short periods in which they need it, before downgrading to the update lock again. As such it can reduce the
* latency for read-only requests, and increase concurrency in applications which otherwise would use a read-write lock.
*
* Lock Acquisition Paths
*
* Lock type Associated Permissions Lock acquisition paths Lock downgrade paths Prevented with exception *
* Read Read (shared) None → Read
Read → Read (reentrant) Read → None Read → Update
Read → Write
* Update Read (shared) None → Update
Update → Update (reentrant)
Write → Update (reentrant) Update → None Update → Read
* Write Read (exclusive)
Write (exclusive) None → Write
Update → Write
Write → Write (reentrant) Write → Update
Write → None Write → Read
*
* * An IllegalStateException will be thrown if a thread holding a regular read lock tries to acquire the
* update or write lock, or if a thread holding the update or write lock tries to acquire a regular read lock.
*
* @author Niall Gallagher
*/
public class ReentrantReadWriteUpdateLock implements ReadWriteUpdateLock {
final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
final Lock updateMutex = new ReentrantLock();
final ReadLock readLock = new ReadLock();
final UpdateLock updateLock = new UpdateLock();
final WriteLock writeLock = new WriteLock();
@Override
public Lock updateLock() {
return updateLock;
}
@Override
public Lock readLock() {
return readLock;
}
@Override
public Lock writeLock() {
return writeLock;
}
static abstract class HoldCountLock implements Lock {
static class HoldCount { int value; }
final ThreadLocal threadHoldCount = new ThreadLocal() {
@Override
protected HoldCount initialValue() {
return new HoldCount();
}
};
final Lock backingLock;
public HoldCountLock(Lock backingLock) {
this.backingLock = backingLock;
}
HoldCount holdCount() {
return threadHoldCount.get();
}
@Override
public void lock() {
validatePreconditions();
backingLock.lock();
holdCount().value++;
}
@Override
public void lockInterruptibly() throws InterruptedException {
validatePreconditions();
backingLock.lockInterruptibly();
holdCount().value++;
}
@Override
public boolean tryLock() {
validatePreconditions();
if (backingLock.tryLock()) {
holdCount().value++;
return true;
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
validatePreconditions();
if (backingLock.tryLock(time, unit)) {
holdCount().value++;
return true;
}
return false;
}
@Override
public void unlock() {
backingLock.unlock();
holdCount().value--;
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException("This lock does not support conditions");
}
abstract void validatePreconditions();
}
class ReadLock extends HoldCountLock {
public ReadLock() {
super(readWriteLock.readLock());
}
void validatePreconditions() {
if (updateLock.holdCount().value > 0) {
throw new IllegalStateException("Cannot acquire read lock, as this thread previously acquired and must first release the update lock");
}
}
}
class UpdateLock extends HoldCountLock {
public UpdateLock() {
super(updateMutex);
}
void validatePreconditions() {
if (readLock.holdCount().value > 0) {
throw new IllegalStateException("Cannot acquire update lock, as this thread previously acquired and must first release the read lock");
}
}
}
class WriteLock implements Lock {
@Override
public void lock() {
validatePreconditions();
// Acquire UPDATE lock again, even if calling thread might already hold it.
// This allow threads to go from both NONE -> WRITE and from UPDATE -> WRITE.
// This also ensures that only the thread holding the single UPDATE lock,
// can request the WRITE lock...
Locks.lockAll(updateLock, readWriteLock.writeLock());
}
@Override
public void lockInterruptibly() throws InterruptedException {
validatePreconditions();
Locks.lockInterruptiblyAll(updateLock, readWriteLock.writeLock());
}
@Override
public boolean tryLock() {
validatePreconditions();
return Locks.tryLockAll(updateLock, readWriteLock.writeLock());
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
validatePreconditions();
return Locks.tryLockAll(time, unit, updateLock, readWriteLock.writeLock());
}
@Override
public void unlock() {
Locks.unlockAll(readWriteLock.writeLock(), updateLock);
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException("This lock does not support conditions");
}
void validatePreconditions() {
if (readLock.holdCount().value > 0) {
throw new IllegalStateException("Cannot acquire write lock, as this thread previously acquired and must first release the read lock");
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy