001/* 002 * Copyright 2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2018 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 (final 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}