/* * 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.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * An extension of a {@link ReentrantReadWriteLock} that has a preference for * either readers or writers. * <p> * A {@link PriorityReadWriteLock} is useful in cases when there is a desire to * prefer readers over writers or vice versa under contention. Prioritized * actors will try to grab their locks immediately whereas unprivileged ones * will deferentially linger for a while before attempting to grab theirs. This * means that a prioritized actor will always grab her lock before an * unprioritized one who tries to grab his at the same time. * </p> * <p> * These locks have logic to ensure that unprioritized actors only defer to * prioritized ones in the event that there is contention for the lock. If there * is none, then unprioritized actors will generally grab their locks * immediately. * </p> * * @author Jeff Nelson */ @SuppressWarnings("serial") public class PriorityReadWriteLock extends ReentrantReadWriteLock { /** * Return a {@link PriorityReadWriteLock} that has a preference for * readers over writers under contention. * * @return the lock */ public static PriorityReadWriteLock prioritizeReads() { return new PriorityReadWriteLock(true); } /** * Return a {@link PriorityReadWriteLock} that has a preference for * writers over readers under contention. * * @return the lock */ public static PriorityReadWriteLock prioritizeWrites() { return new PriorityReadWriteLock(false); } /** * A flag that indicates that unprivileged actor must spin before trying to * grab the lock. By default, this is set to {@code false}, but a privileged * actor will set this to {@code true} when they try to grab the lock. * Conversely, unprivileged actors always set this to {@code false}, which * means that they generally won't ever spin while there aren't any lock * attempts from privileged actors. */ private volatile boolean forceSpin = false; /** * The lock that is returned from the {@link #readLock()} method. */ private final ReadLock readLock; /** * The lock that is returned from the {@link #writeLock()} method. */ private final WriteLock writeLock; /** * Construct a new instance. If {@code privilegedReads} is {@code true}, * then the resulting lock with be biased towards read locking and biased * against write locking. The opposite is true if the parameter is * {@code false}. * * @param prioritizeReads */ private PriorityReadWriteLock(boolean prioritizeReads) { readLock = prioritizeReads ? new PriorityReadLock(this) : new UnpriorityReadLock(this); writeLock = prioritizeReads ? new UnpriorityWriteLock(this) : new PriorityWriteLock(this); } @Override public ReadLock readLock() { return readLock; } @Override public WriteLock writeLock() { return writeLock; } /** * Spin if it is necessary to do so. */ private void trySpin() { if(forceSpin) { try { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } /** * An {@link ReadLock} that does not defer to writers. * * @author Jeff Nelson */ private final class PriorityReadLock extends ReadLock { /** * Construct a new instance. * * @param lock */ protected PriorityReadLock(PriorityReadWriteLock lock) { super(lock); } @Override public void lock() { forceSpin = true; super.lock(); } @Override public void lockInterruptibly() throws InterruptedException { forceSpin = true; super.lockInterruptibly(); } @Override public boolean tryLock() { forceSpin = true; return super.tryLock(); } @Override public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { forceSpin = true; return super.tryLock(timeout, unit); } } /** * An {@link WriteLock} that does not defer to readers. * * @author Jeff Nelson */ private final class PriorityWriteLock extends WriteLock { /** * Construct a new instance. * * @param lock */ protected PriorityWriteLock(PriorityReadWriteLock lock) { super(lock); } @Override public void lock() { forceSpin = true; super.lock(); } @Override public void lockInterruptibly() throws InterruptedException { forceSpin = true; super.lockInterruptibly(); } @Override public boolean tryLock() { forceSpin = true; return super.tryLock(); } @Override public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { forceSpin = true; return super.tryLock(timeout, unit); } } /** * An {@link ReadLock} that defers to writers under contention. * * @author Jeff Nelson */ private final class UnpriorityReadLock extends ReadLock { /** * Construct a new instance. * * @param lock */ protected UnpriorityReadLock(PriorityReadWriteLock lock) { super(lock); } @Override public void lock() { trySpin(); super.lock(); forceSpin = false; } @Override public void lockInterruptibly() throws InterruptedException { trySpin(); super.lockInterruptibly(); forceSpin = false; } @Override public boolean tryLock() { trySpin(); forceSpin = false; return super.tryLock(); } @Override public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { trySpin(); if(super.tryLock(timeout, unit)) { forceSpin = false; return true; } else { return false; } } } /** * An {@link WriteLock} that defers to readers under contention. * * @author Jeff Nelson */ private final class UnpriorityWriteLock extends WriteLock { /** * Constructa new instance. * * @param lock */ protected UnpriorityWriteLock(PriorityReadWriteLock lock) { super(lock); } @Override public void lock() { trySpin(); super.lock(); forceSpin = false; } @Override public void lockInterruptibly() throws InterruptedException { trySpin(); super.lockInterruptibly(); forceSpin = false; } @Override public boolean tryLock() { trySpin(); forceSpin = false; return super.tryLock(); } @Override public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { trySpin(); if(super.tryLock(timeout, unit)) { forceSpin = false; return true; } else { return false; } } } }