1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/**
4 *******************************************************************************
5 * Copyright (C) 2001-2013, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9package com.ibm.icu.impl;
10
11import java.util.concurrent.locks.ReentrantReadWriteLock;
12
13
14/**
15 * <p>A Reader/Writer lock originally written for ICU service
16 * implementation. The internal implementation was replaced
17 * with the JDK's stock read write lock (ReentrantReadWriteLock)
18 * for ICU 52.</p>
19 *
20 * <p>This assumes that there will be little writing contention.
21 * It also doesn't allow active readers to acquire and release
22 * a write lock, or deal with priority inversion issues.</p>
23 *
24 * <p>Access to the lock should be enclosed in a try/finally block
25 * in order to ensure that the lock is always released in case of
26 * exceptions:<br><pre>
27 * try {
28 *     lock.acquireRead();
29 *     // use service protected by the lock
30 * }
31 * finally {
32 *     lock.releaseRead();
33 * }
34 * </pre></p>
35 *
36 * <p>The lock provides utility methods getStats and clearStats
37 * to return statistics on the use of the lock.</p>
38 */
39public class ICURWLock {
40    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
41
42    private Stats stats = null;
43
44    /**
45     * Internal class used to gather statistics on the RWLock.
46     */
47    public final static class Stats {
48        /**
49         * Number of times read access granted (read count).
50         */
51        public int _rc;
52
53        /**
54         * Number of times concurrent read access granted (multiple read count).
55         */
56        public int _mrc;
57
58        /**
59         * Number of times blocked for read (waiting reader count).
60         */
61        public int _wrc; // wait for read
62
63        /**
64         * Number of times write access granted (writer count).
65         */
66        public int _wc;
67
68        /**
69         * Number of times blocked for write (waiting writer count).
70         */
71        public int _wwc;
72
73        private Stats() {
74        }
75
76        private Stats(int rc, int mrc, int wrc, int wc, int wwc) {
77            this._rc = rc;
78            this._mrc = mrc;
79            this._wrc = wrc;
80            this._wc = wc;
81            this._wwc = wwc;
82        }
83
84        private Stats(Stats rhs) {
85            this(rhs._rc, rhs._mrc, rhs._wrc, rhs._wc, rhs._wwc);
86        }
87
88        /**
89         * Return a string listing all the stats.
90         */
91        @Override
92        public String toString() {
93            return " rc: " + _rc +
94                " mrc: " + _mrc +
95                " wrc: " + _wrc +
96                " wc: " + _wc +
97                " wwc: " + _wwc;
98        }
99    }
100
101    /**
102     * Reset the stats.  Returns existing stats, if any.
103     */
104    public synchronized Stats resetStats() {
105        Stats result = stats;
106        stats = new Stats();
107        return result;
108    }
109
110    /**
111     * Clear the stats (stop collecting stats).  Returns existing stats, if any.
112     */
113    public synchronized Stats clearStats() {
114        Stats result = stats;
115        stats = null;
116        return result;
117    }
118
119    /**
120     * Return a snapshot of the current stats.  This does not reset the stats.
121     */
122    public synchronized Stats getStats() {
123        return stats == null ? null : new Stats(stats);
124    }
125
126    /**
127     * <p>Acquire a read lock, blocking until a read lock is
128     * available.  Multiple readers can concurrently hold the read
129     * lock.</p>
130     *
131     * <p>If there's a writer, or a waiting writer, increment the
132     * waiting reader count and block on this.  Otherwise
133     * increment the active reader count and return.  Caller must call
134     * releaseRead when done (for example, in a finally block).</p>
135     */
136    public void acquireRead() {
137        if (stats != null) {    // stats is null by default
138            synchronized (this) {
139                stats._rc++;
140                if (rwl.getReadLockCount() > 0) {
141                    stats._mrc++;
142                }
143                if (rwl.isWriteLocked()) {
144                    stats._wrc++;
145                }
146            }
147        }
148        rwl.readLock().lock();
149    }
150
151    /**
152     * <p>Release a read lock and return.  An error will be thrown
153     * if a read lock is not currently held.</p>
154     *
155     * <p>If this is the last active reader, notify the oldest
156     * waiting writer.  Call when finished with work
157     * controlled by acquireRead.</p>
158     */
159    public void releaseRead() {
160        rwl.readLock().unlock();
161    }
162
163    /**
164     * <p>Acquire the write lock, blocking until the write lock is
165     * available.  Only one writer can acquire the write lock, and
166     * when held, no readers can acquire the read lock.</p>
167     *
168     * <p>If there are no readers and no waiting writers, mark as
169     * having an active writer and return.  Otherwise, add a lock to the
170     * end of the waiting writer list, and block on it.  Caller
171     * must call releaseWrite when done (for example, in a finally
172     * block).<p>
173     */
174    public void acquireWrite() {
175        if (stats != null) {    // stats is null by default
176            synchronized (this) {
177                stats._wc++;
178                if (rwl.getReadLockCount() > 0 || rwl.isWriteLocked()) {
179                    stats._wwc++;
180                }
181            }
182        }
183        rwl.writeLock().lock();
184    }
185
186    /**
187     * <p>Release the write lock and return.  An error will be thrown
188     * if the write lock is not currently held.</p>
189     *
190     * <p>If there are waiting readers, make them all active and
191     * notify all of them.  Otherwise, notify the oldest waiting
192     * writer, if any.  Call when finished with work controlled by
193     * acquireWrite.</p>
194     */
195    public void releaseWrite() {
196        rwl.writeLock().unlock();
197    }
198}
199