File: KeyedReentrantReadWriteLock.java

package info (click to toggle)
tomcat11 11.0.11-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 47,028 kB
  • sloc: java: 366,244; xml: 55,681; jsp: 4,783; sh: 1,304; perl: 324; makefile: 25; ansic: 14
file content (179 lines) | stat: -rw-r--r-- 6,811 bytes parent folder | download | duplicates (2)
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();
    }
}