1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.concurrent;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import org.apache.tomcat.util.res.StringManager;
/**
* Provides a reentrant read/write lock for a given key. Any locks obtained from an instance of this class using the
* same key will use the same underlying reentrant read/write lock as long as at least one lock for that key remains in
* use. Once no locks are in use for the given key, the lock is eligible for GC and the next lock obtained using that
* key will use a new underlying reentrant read/write lock.
* <p>
* The class is used when Tomcat needs to manage concurrent access to components identified by a key (e.g. sessions).
* <p>
* The map of keys to locks is maintained so that locks are created as required and removed when no longer used.
* <p>
* The locks provided by this class only implement {@code Lock#lock()} and {@code Lock#unlock()}. All other methods will
* throw {@code UnsupportedOperationException}.
*/
public class KeyedReentrantReadWriteLock {
private final Map<String,CountedLock> locksMap = new HashMap<>();
/**
* Obtain the reentrant read/write lock for the given key.
*
* @param key The key for which the lock should be obtained
*
* @return A reentrant read/write lock for the given key
*/
public ReadWriteLock getLock(String key) {
return new ReadWriteLockImpl(locksMap, key);
}
/*
* Reentrant read/write lock implementation that is passed back to the caller. It provides the lock wrappers that
* track usage.
*/
private static class ReadWriteLockImpl implements ReadWriteLock {
private final Map<String,CountedLock> locksMap;
private final String key;
private volatile Lock readLock;
private volatile Lock writeLock;
ReadWriteLockImpl(Map<String,CountedLock> locksMap, String key) {
this.locksMap = locksMap;
this.key = key;
}
@Override
public Lock readLock() {
if (readLock == null) {
readLock = new LockImpl(locksMap, key, ReentrantReadWriteLock::readLock);
}
return readLock;
}
@Override
public Lock writeLock() {
if (writeLock == null) {
writeLock = new LockImpl(locksMap, key, ReentrantReadWriteLock::writeLock);
}
return writeLock;
}
}
/*
* Lock wrapper implementation that provides both read locks and write locks from the underlying lock and tracks
* their usage. Most of the methods throw UnsupportedOperationException as Tomcat does not (currently) require
* implementations of those methods.
*/
private static class LockImpl implements Lock {
private static final StringManager sm = StringManager.getManager(LockImpl.class);
private final Map<String,CountedLock> locksMap;
private final String key;
private final Function<ReentrantReadWriteLock,Lock> function;
LockImpl(Map<String,CountedLock> locksMap, String key, Function<ReentrantReadWriteLock,Lock> function) {
this.locksMap = locksMap;
this.key = key;
this.function = function;
}
@Override
public void lock() {
CountedLock countedLock = null;
synchronized (locksMap) {
// Lookup / create the counted lock for the given key
countedLock = locksMap.compute(key, (k, v) -> v == null ? new CountedLock() : v);
// Increment usage count inside the sync block to ensure other threads are aware key is in use.
countedLock.count.incrementAndGet();
}
// Lock outside of the sync block in case the call to lock() blocks.
function.apply(countedLock.reentrantLock).lock();
}
@Override
public void unlock() {
CountedLock countedLock = null;
// Unlocking so a lock should exist in the map for the given key.
synchronized (locksMap) {
countedLock = locksMap.get(key);
}
if (countedLock == null) {
throw new IllegalStateException(sm.getString("lockImpl.unlockWithoutLock"));
}
// No need to unlock inside sync block, so don't.
function.apply(countedLock.reentrantLock).unlock();
synchronized (locksMap) {
/*
* Decrement usage count and check for zero inside the sync block to ensure usage tracking is consistent
* across multiple threads.
*/
if (countedLock.count.decrementAndGet() == 0) {
locksMap.remove(key);
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public boolean tryLock() {
throw new UnsupportedOperationException();
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException();
}
}
/*
* Holds the underlying reentrant read/write lock and the counter that tracks usage.
*/
private static class CountedLock {
AtomicInteger count = new AtomicInteger();
ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
}
}
|