/* * 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.Condition; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.google.common.base.Stopwatch; /** * A {@link ReadWriteSharedLock} is a form of a reentrant lock that permits * either multiple concurrent readers OR multiple concurrent writers. * * @author Jeff Nelson */ @SuppressWarnings("serial") public class ReadWriteSharedLock extends ReentrantReadWriteLock { /** * These are the internal locks that are used to actually control concurrent * access. We use a distinct traditional ReentrantReadWriteLock to represent * the read and write locks. In both cases, we only use the read view of the * locks so that multiple actors can concurrent access. */ private final ReentrantReadWriteLock readers = new ReentrantReadWriteLock(); /** * These are the internal locks that are used to actually control concurrent * access. We use a distinct traditional ReentrantReadWriteLock to represent * the read and write locks. In both cases, we only use the read view of the * locks so that multiple actors can concurrent access. */ private final ReentrantReadWriteLock writers = new ReentrantReadWriteLock(); /** * Return from the {@link #readLock()} method. */ private final ReadLock readLock = new SharedReadLock(); /** * Return from the {@link #writeLock()} method. */ private final WriteLock writeLock = new SharedWriteLock(); /** * Return the number of reentrant read holds on this lock by the current * thread. A reader thread has a hold on a lock for each lock action that is * not matched by an unlock action. * * @return the number of read holds on the read lock by the current thread, * or zero if the read lock is not held by the current thread */ public int getReadHoldCount() { return readers.getReadHoldCount(); } /** * Return the number read locks held for this lock. This method is designed * for use in monitoring systems, not for synchronization control. * * @return the number of read locks held */ public int getReadLockCount() { return readers.getReadLockCount(); } /** * Return the number of reentrant write holds on this lock by the current * thread. A writer thread has a hold on a lock for each lock action that is * not matched by an unlock action. * * @return the number of write holds on the read lock by the current thread, * or zero if the write lock is not held by the current thread */ public int getWriteHoldCount() { return writers.getReadHoldCount(); } /** * Return the number write locks held for this lock. This method is designed * for use in monitoring systems, not for synchronization control. * * @return the number of write locks held */ public int getWriteLockCount() { return writers.getReadLockCount(); } /** * Return {@code true} if the write lock is held by the current thread. * * @return {@code true} if the write lock is held by the current thread */ public boolean isWriteLockedByCurrentThread() { return writers.getReadHoldCount() > 0; } @Override public ReadLock readLock() { return readLock; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.getClass()); sb.append("$"); sb.append(System.identityHashCode(this)); sb.append("["); sb.append("readers="); sb.append(readers.getReadLockCount()); sb.append("("); sb.append(readers.isWriteLocked()); sb.append(")"); sb.append(", "); sb.append("writers="); sb.append(writers.getReadLockCount()); sb.append("("); sb.append(writers.isWriteLocked()); sb.append(")"); sb.append("]"); return sb.toString(); } @Override public WriteLock writeLock() { return writeLock; } /** * An {@link WriteLock} that can be grabbed by multiple writers at the same * time while blocking all readers. * * @author Jeff Nelson */ private class SharedWriteLock extends WriteLock { /** * Construct a new instance. * * @param lock */ protected SharedWriteLock() { super(ReadWriteSharedLock.this); } @Override public void lock() { boolean locked = false; while (!locked) { readers.writeLock().lock(); locked = writers.readLock().tryLock(); readers.writeLock().unlock(); if(!locked) { Thread.yield(); } } } @Override public void lockInterruptibly() throws InterruptedException { boolean locked = false; while (!locked) { readers.writeLock().lockInterruptibly(); locked = writers.readLock().tryLock(); readers.writeLock().unlock(); if(!locked) { Thread.yield(); } } } @Override public boolean tryLock() { for (;;) { if(readers.getReadLockCount() == 0) { if(readers.writeLock().tryLock()) { try { if(writers.readLock().tryLock()) { return true; } } finally { readers.writeLock().unlock(); } Thread.yield(); } } else { return false; } } } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { Stopwatch watch = Stopwatch.createStarted(); for (;;) { if(readers.getReadLockCount() == 0) { if(readers.writeLock().tryLock(time, unit)) { watch.stop(); long elapsed = watch.elapsed(unit); time = time - elapsed; watch.start(); try { if(writers.readLock().tryLock(time, unit)) { return true; } } finally { readers.writeLock().unlock(); } watch.stop(); elapsed = watch.elapsed(unit); time = time - elapsed; watch.start(); Thread.yield(); } } else { return false; } } } @Override public void unlock() { writers.readLock().unlock(); } @Override public Condition newCondition() { return writers.readLock().newCondition(); } @Override public boolean isHeldByCurrentThread() { return writers.getReadHoldCount() > 0; } @Override public int getHoldCount() { return writers.getReadHoldCount(); } } /** * An {@link ReadLock} that can be grabbed by multiple readers at the same * time while blocking all writers. * * @author Jeff Nelson */ private class SharedReadLock extends ReadLock { /** * Construct a new instance. * * @param lock */ protected SharedReadLock() { super(ReadWriteSharedLock.this); } @Override public void lock() { boolean locked = false; while (!locked) { if(writers.getReadHoldCount() > 0) { // this thread already has the write lock, so it can // reentrantly grab the read lock locked = readers.readLock().tryLock(); } else { writers.writeLock().lock(); locked = readers.readLock().tryLock(); writers.writeLock().unlock(); } if(!locked) { Thread.yield(); } } } @Override public void lockInterruptibly() throws InterruptedException { boolean locked = false; while (!locked) { if(writers.getReadHoldCount() > 0) { // this thread already has the write lock, so it can // reentrantly grab the read lock locked = readers.readLock().tryLock(); } writers.writeLock().lockInterruptibly(); locked = readers.readLock().tryLock(); writers.writeLock().unlock(); if(!locked) { Thread.yield(); } } } @Override public boolean tryLock() { for (;;) { if(writers.getReadLockCount() == 0) { if(writers.getReadHoldCount() > 0) { // this thread already has the write lock, so it can // reentrantly grab the read lock readers.readLock().lock(); return true; } else if(writers.writeLock().tryLock()) { try { if(readers.readLock().tryLock()) { return true; } } finally { writers.writeLock().unlock(); } Thread.yield(); } } else { return false; } } } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { Stopwatch watch = Stopwatch.createStarted(); for (;;) { if(writers.getReadLockCount() == 0) { if(writers.getReadHoldCount() > 0) { // this thread already has the write lock, so it can // reentrantly grab the read lock readers.readLock().lock(); return true; } if(writers.writeLock().tryLock(time, unit)) { watch.stop(); long elapsed = watch.elapsed(unit); time = time - elapsed; watch.start(); try { if(readers.readLock().tryLock(time, unit)) { return true; } } finally { writers.writeLock().unlock(); } watch.stop(); elapsed = watch.elapsed(unit); time = time - elapsed; watch.start(); Thread.yield(); } } else { return false; } } } @Override public void unlock() { readers.readLock().unlock(); } @Override public Condition newCondition() { return readers.readLock().newCondition(); } } }