/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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.cinchapi.concourse.server.concurrent; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.cinchapi.concourse.annotate.PackagePrivate; /** * A decorated {@link ReentrantReadWriteLock} that has a counter to keep track * of references. References are decremented when the read or write lock is * released. The subclass should increment the reference count when the lock is * considered to be <em>grabbed</em> * * @author Jeff Nelson */ @PackagePrivate @SuppressWarnings("serial") class ReferenceCountingLock extends ReentrantReadWriteLock { // NOTE: This class does not define hashCode() or equals() because the // defaults are the desired behaviour @Override public int getReadLockCount() { return decorated.getReadLockCount(); } @Override public boolean isWriteLocked() { return decorated.isWriteLocked(); } @Override public boolean isWriteLockedByCurrentThread() { return decorated.isWriteLockedByCurrentThread(); } @Override public int getWriteHoldCount() { return decorated.getWriteHoldCount(); } @Override public int getReadHoldCount() { return decorated.getReadHoldCount(); } @Override public boolean hasWaiters(Condition condition) { return decorated.hasWaiters(condition); } @Override public int getWaitQueueLength(Condition condition) { return decorated.getWaitQueueLength(condition); } /** * A counter that keeps track of "references" to this lock. Each time * the lock is requested, the counter is incremented. Each time the lock * is released, the counter is decremented. This counter helps to * perform periodic cleanup. */ @PackagePrivate final AtomicInteger refs = new AtomicInteger(0); /** * The lock that is decorated by this reference counting wrapper. */ private final ReentrantReadWriteLock decorated; /** * A reference to the readLock that performs the work in the * {@link #readLock()} method. */ private final ReadLock readLock0; /** * A reference to the writeLock that performs the work in the * {@link #writeLock()} method. */ private final WriteLock writeLock0; /** * The {@link ReadLock} instance that is returned from the * {@link #readLock()} method. */ private final ReadLock readLock; /** * The {@link WriteLock} instance that is returned from the * {@link #writeLock()} method. */ private final WriteLock writeLock; /** * Construct a new instance. * * @param decorated */ ReferenceCountingLock(ReentrantReadWriteLock decorated) { this.decorated = decorated; this.readLock0 = decorated.readLock(); this.writeLock0 = decorated.writeLock(); this.readLock = new DecoratedReadLock(); this.writeLock = new DecoratedWriteLock(); } /** * A {@link WriteLock} that handles reference counting. * * @author Jeff Nelson */ private class DecoratedWriteLock extends WriteLock { /** * Construct a new instance. */ protected DecoratedWriteLock() { super(decorated); } @Override public void lock() { beforeWriteLock(); writeLock0.lock(); afterWriteLock(); } @Override public boolean tryLock() { if(tryBeforeWriteLock() && writeLock0.tryLock()) { afterWriteLock(); return true; } else { return false; } } @Override public void unlock() { writeLock0.unlock(); afterWriteUnlock(decorated); refs.decrementAndGet(); } } /** * A {@link ReadLock} that handles reference counting. * * @author Jeff Nelson */ private class DecoratedReadLock extends ReadLock { /** * Construct a new instance. */ protected DecoratedReadLock() { super(decorated); } @Override public void lock() { beforeReadLock(); readLock0.lock(); afterReadLock(); } @Override public boolean tryLock() { if(tryBeforeReadLock() && readLock0.tryLock()) { afterReadLock(); return true; } else { return false; } } @Override public void unlock() { readLock0.unlock(); afterReadUnlock(decorated); refs.decrementAndGet(); } } @Override public ReadLock readLock() { return readLock; } @Override public WriteLock writeLock() { return writeLock; } /** * This (optional) method is always run before grabbing the read lock. It is * useful for cases where it is necessary to check some additional state * before proceeding to the locking routing. */ protected void beforeReadLock() {/* noop */} /** * This (optional) method is always run after grabbing the read lock. It is * useful for cases where it is necessary to update some additional state. */ protected void afterReadLock() {/* noop */} /** * This (optional) method is always run after releasing the read lock. It is * useful for cases where it is necessary to cleanup state. * * @param instance */ protected void afterReadUnlock(ReentrantReadWriteLock instance) {/* noop */} /** * This (optional) method is always run before grabbing the write lock. It * is useful for cases where it is necessary to check some additional state * before proceeding to the locking routing. */ protected void beforeWriteLock() {/* noop */} /** * This (optional) method is always run after grabbing the write lock. It is * useful for cases where it is necessary to update some additional state. */ protected void afterWriteLock() {/* noop */} /** * This (optional) method is always run after releasing the write lock. It * is useful for cases where it is necessary to cleanup state. * * @param instance */ protected void afterWriteUnlock(ReentrantReadWriteLock instance) {/* noop */} /** * This (optional) method is always run before an attempt is made to grab * the read lock. It is useful for cases where it is necessary to check some * additional state to determine if the read lock is grabable. * * @return {@code true} if the routine determines an attempt should be made * to grab the read lock */ protected boolean tryBeforeReadLock() { return true; } /** * This (optional) method is always run before an attempt is made to grab * the write lock. It is useful for cases where it is necessary to check * some * additional state to determine if the write lock is grabable. * * @return {@code true} if the routine determines an attempt should be made * to grab the write lock */ protected boolean tryBeforeWriteLock() { return true; } }