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