001/*
002 * Copyright 2018-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2018-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util;
022
023
024
025import java.io.Closeable;
026import java.util.concurrent.TimeUnit;
027import java.util.concurrent.TimeoutException;
028import java.util.concurrent.locks.ReentrantReadWriteLock;
029
030import static com.unboundid.util.UtilityMessages.*;
031
032
033
034/**
035 * This class provides an implementation of a reentrant read-write lock that can
036 * be used with the Java try-with-resources facility.  With a read-write lock,
037 * either exactly one thread can hold the write lock while no other threads hold
038 * read locks, or zero or more threads can hold read locks while no thread holds
039 * the write lock.  The one exception to this policy is that the thread that
040 * holds the write lock can downgrade will be permitted to acquire a read lock
041 * before it releases the write lock to downgrade from a write lock to a read
042 * lock while ensuring that no other thread is permitted to acquire the write
043 * lock while it is in the process of downgrading.
044 * <BR><BR>
045 * This class does not implement the
046 * {@code java.util.concurrent.locks.ReadWriteLock} interface in order to ensure
047 * that it can only be used through the try-with-resources mechanism, but it
048 * uses a {@code java.util.concurrent.locks.ReentrantReadWriteLock} behind the
049 * scenes to provide its functionality.
050 * <BR><BR>
051 * <H2>Example</H2>
052 * The following example demonstrates how to use this lock using the Java
053 * try-with-resources facility:
054 * <PRE>
055 * // Wait for up to 5 seconds to acquire the lock.
056 * try (CloseableReadWriteLock.WriteLock writeLock =
057 *           closeableReadWriteLock.tryLock(5L, TimeUnit.SECONDS))
058 * {
059 *   // NOTE:  If you don't reference the lock object inside the try block, the
060 *   // compiler will issue a warning.
061 *   writeLock.avoidCompilerWarning();
062 *
063 *   // Do something while the lock is held.  The lock will automatically be
064 *   // released once code execution leaves this block.
065 * }
066 * catch (final InterruptedException e)
067 * {
068 *   // The thread was interrupted before the lock could be acquired.
069 * }
070 * catch (final TimeoutException)
071 * {
072 *   // The lock could not be acquired within the specified 5-second timeout.
073 * }
074 * </PRE>
075 */
076@Mutable()
077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078public final class CloseableReadWriteLock
079{
080  // The closeable read lock.
081  private final ReadLock readLock;
082
083  // The Java lock that is used behind the scenes for all locking functionality.
084  private final ReentrantReadWriteLock readWriteLock;
085
086  // The closeable write lock.
087  private final WriteLock writeLock;
088
089
090
091  /**
092   * Creates a new instance of this read-write lock with a non-fair ordering
093   * policy.
094   */
095  public CloseableReadWriteLock()
096  {
097    this(false);
098  }
099
100
101
102  /**
103   * Creates a new instance of this read-write lock with the specified ordering
104   * policy.
105   *
106   * @param  fair  Indicates whether the lock should use fair ordering.  If
107   *               {@code true}, then if multiple threads are waiting on the
108   *               lock, then the one that has been waiting the longest is the
109   *               one that will get it.  If {@code false}, then no guarantee
110   *               will be made about the order.  Fair ordering can incur a
111   *               performance penalty.
112   */
113  public CloseableReadWriteLock(final boolean fair)
114  {
115    readWriteLock = new ReentrantReadWriteLock(fair);
116    readLock = new ReadLock(readWriteLock.readLock());
117    writeLock = new WriteLock(readWriteLock.writeLock());
118  }
119
120
121
122  /**
123   * Acquires the write lock, blocking until the lock is available.
124   *
125   * @return  The {@link WriteLock} instance that may be used to perform the
126   *          unlock via the try-with-resources facility.
127   */
128  public WriteLock lockWrite()
129  {
130    readWriteLock.writeLock().lock();
131    return writeLock;
132  }
133
134
135
136  /**
137   * Acquires the write lock, blocking until the lock is available.
138   *
139   * @return  The {@link WriteLock} instance that may be used to perform the
140   *          unlock via the try-with-resources facility.
141   *
142   * @throws  InterruptedException  If the thread is interrupted while waiting
143   *                                to acquire the lock.
144   */
145  public WriteLock lockWriteInterruptibly()
146         throws InterruptedException
147  {
148    readWriteLock.writeLock().lockInterruptibly();
149    return writeLock;
150  }
151
152
153
154  /**
155   * Tries to acquire the write lock, waiting up to the specified length of time
156   * for it to become available.
157   *
158   * @param  waitTime  The maximum length of time to wait for the lock.  It must
159   *                   be greater than zero.
160   * @param  timeUnit  The time unit that should be used when evaluating the
161   *                   {@code waitTime} value.
162   *
163   * @return  The {@link WriteLock} instance that may be used to perform the
164   *          unlock via the try-with-resources facility.
165   *
166   * @throws  InterruptedException  If the thread is interrupted while waiting
167   *                                to acquire the lock.
168   *
169   * @throws  TimeoutException  If the lock could not be acquired within the
170   *                            specified length of time.
171   */
172  public WriteLock tryLockWrite(final long waitTime, final TimeUnit timeUnit)
173         throws InterruptedException, TimeoutException
174  {
175    if (waitTime <= 0)
176    {
177      Validator.violation(
178           "CloseableLock.tryLockWrite.waitTime must be greater than zero.  " +
179                "The provided value was " + waitTime);
180    }
181
182    if (readWriteLock.writeLock().tryLock(waitTime, timeUnit))
183    {
184      return writeLock;
185    }
186    else
187    {
188      throw new TimeoutException(
189           ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_WRITE_TIMEOUT.get(
190                StaticUtils.millisToHumanReadableDuration(
191                     timeUnit.toMillis(waitTime))));
192    }
193  }
194
195
196
197  /**
198   * Acquires a read lock, blocking until the lock is available.
199   *
200   * @return  The {@link ReadLock} instance that may be used to perform the
201   *          unlock via the try-with-resources facility.
202   */
203  public ReadLock lockRead()
204  {
205    readWriteLock.readLock().lock();
206    return readLock;
207  }
208
209
210
211  /**
212   * Acquires a read lock, blocking until the lock is available.
213   *
214   * @return  The {@link ReadLock} instance that may be used to perform the
215   *          unlock via the try-with-resources facility.
216   *
217   * @throws  InterruptedException  If the thread is interrupted while waiting
218   *                                to acquire the lock.
219   */
220  public ReadLock lockReadInterruptibly()
221         throws InterruptedException
222  {
223    readWriteLock.readLock().lockInterruptibly();
224    return readLock;
225  }
226
227
228
229  /**
230   * Tries to acquire a read lock, waiting up to the specified length of time
231   * for it to become available.
232   *
233   * @param  waitTime  The maximum length of time to wait for the lock.  It must
234   *                   be greater than zero.
235   * @param  timeUnit  The time unit that should be used when evaluating the
236   *                   {@code waitTime} value.
237   *
238   * @return  The {@link ReadLock} instance that may be used to perform the
239   *          unlock via the try-with-resources facility.
240   *
241   * @throws  InterruptedException  If the thread is interrupted while waiting
242   *                                to acquire the lock.
243   *
244   * @throws  TimeoutException  If the lock could not be acquired within the
245   *                            specified length of time.
246   */
247  public ReadLock tryLockRead(final long waitTime, final TimeUnit timeUnit)
248         throws InterruptedException, TimeoutException
249  {
250    if (waitTime <= 0)
251    {
252      Validator.violation(
253           "CloseableLock.tryLockRead.waitTime must be greater than zero.  " +
254                "The provided value was " + waitTime);
255    }
256
257    if (readWriteLock.readLock().tryLock(waitTime, timeUnit))
258    {
259      return readLock;
260    }
261    else
262    {
263      throw new TimeoutException(
264           ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_READ_TIMEOUT.get(
265                StaticUtils.millisToHumanReadableDuration(
266                     timeUnit.toMillis(waitTime))));
267    }
268  }
269
270
271
272  /**
273   * Indicates whether this lock uses fair ordering.
274   *
275   * @return  {@code true} if this lock uses fair ordering, or {@code false} if
276   *          not.
277   */
278  public boolean isFair()
279  {
280    return readWriteLock.isFair();
281  }
282
283
284
285  /**
286   * Indicates whether the write lock is currently held by any thread.
287   *
288   * @return  {@code true} if the write lock is currently held by any thread, or
289   *          {@code false} if not.
290   */
291  public boolean isWriteLocked()
292  {
293    return readWriteLock.isWriteLocked();
294  }
295
296
297
298  /**
299   * Indicates whether the write lock is currently held by the current thread.
300   *
301   * @return  {@code true} if the write lock is currently held by the current
302   *          thread, or {@code false} if not.
303   */
304  public boolean isWriteLockedByCurrentThread()
305  {
306    return readWriteLock.isWriteLockedByCurrentThread();
307  }
308
309
310
311  /**
312   * Retrieves the number of holds that the current thread has on the write
313   * lock.
314   *
315   * @return  The number of holds that the current thread has on the write lock.
316   */
317  public int getWriteHoldCount()
318  {
319    return readWriteLock.getWriteHoldCount();
320  }
321
322
323
324  /**
325   * Retrieves the number of threads that currently hold the read lock.
326   *
327   * @return  The number of threads that currently hold the read lock.
328   */
329  public int getReadLockCount()
330  {
331    return readWriteLock.getReadLockCount();
332  }
333
334
335
336  /**
337   * Retrieves the number of holds that the current thread has on the read lock.
338   *
339   * @return  The number of holds that the current thread has on the read lock.
340   */
341  public int getReadHoldCount()
342  {
343    return readWriteLock.getReadHoldCount();
344  }
345
346
347
348  /**
349   * Indicates whether any threads are currently waiting to acquire either the
350   * write or read lock.
351   *
352   * @return  {@code true} if any threads are currently waiting to acquire
353   *          either the write or read lock, or {@code false} if not.
354   */
355  public boolean hasQueuedThreads()
356  {
357    return readWriteLock.hasQueuedThreads();
358  }
359
360
361
362  /**
363   * Indicates whether the specified thread is currently waiting to acquire
364   * either the write or read lock.
365   *
366   * @param  thread  The thread for which to make the determination.  It must
367   *                 not be {@code null}.
368   *
369   * @return  {@code true} if the specified thread is currently waiting to
370   *          acquire either the write or read lock, or {@code false} if not.
371   */
372  public boolean hasQueuedThread(final Thread thread)
373  {
374    return readWriteLock.hasQueuedThread(thread);
375  }
376
377
378
379  /**
380   * Retrieves an estimate of the number of threads currently waiting to acquire
381   * either the write or read lock.
382   *
383   * @return  An estimate of the number of threads currently waiting to acquire
384   *          either the write or read lock.
385   */
386  public int getQueueLength()
387  {
388    return readWriteLock.getQueueLength();
389  }
390
391
392
393  /**
394   * Retrieves a string representation of this read-write lock.
395   *
396   * @return  A string representation of this read-write lock.
397   */
398  @Override()
399  public String toString()
400  {
401    return "CloseableReadWriteLock(lock=" + readWriteLock.toString() + ')';
402  }
403
404
405
406  /**
407   * This class provides a {@code Closeable} implementation that may be used to
408   * unlock a {@link CloseableReadWriteLock}'s read lock via Java's
409   * try-with-resources facility.
410   */
411  public final class ReadLock
412         implements Closeable
413  {
414    // The associated read lock.
415    private final ReentrantReadWriteLock.ReadLock lock;
416
417
418
419    /**
420     * Creates a new instance with the provided lock.
421     *
422     * @param  lock  The lock that will be unlocked when the [@link #close()}
423     *               method is called.  This must not be {@code null}.
424     */
425    private ReadLock(final ReentrantReadWriteLock.ReadLock lock)
426    {
427      this.lock = lock;
428    }
429
430
431
432    /**
433     * This method does nothing.  However, calling it inside a try block when
434     * used in the try-with-resources framework can help avoid a compiler
435     * warning that the JVM will give you if you don't reference the
436     * {@code Closeable} object inside the try block.
437     */
438    public void avoidCompilerWarning()
439    {
440      // No implementation is required.
441    }
442
443
444
445    /**
446     * Unlocks the associated lock.
447     */
448    @Override()
449    public void close()
450    {
451      lock.unlock();
452    }
453  }
454
455
456
457  /**
458   * This class provides a {@code Closeable} implementation that may be used to
459   * unlock a {@link CloseableReadWriteLock}'s write lock via Java's
460   * try-with-resources facility.
461   */
462  public final class WriteLock
463         implements Closeable
464  {
465    // The associated read lock.
466    private final ReentrantReadWriteLock.WriteLock lock;
467
468
469
470    /**
471     * Creates a new instance with the provided lock.
472     *
473     * @param  lock  The lock that will be unlocked when the [@link #close()}
474     *               method is called.  This must not be {@code null}.
475     */
476    private WriteLock(final ReentrantReadWriteLock.WriteLock lock)
477    {
478      this.lock = lock;
479    }
480
481
482
483    /**
484     * This method does nothing.  However, calling it inside a try block when
485     * used in the try-with-resources framework can help avoid a compiler
486     * warning that the JVM will give you if you don't reference the
487     * {@code Closeable} object inside the try block.
488     */
489    public void avoidCompilerWarning()
490    {
491      // No implementation is required.
492    }
493
494
495
496    /**
497     * Unlocks the associated lock.
498     */
499    @Override()
500    public void close()
501    {
502      lock.unlock();
503    }
504  }
505}