001/* 002 * Copyright 2018-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2018-2024 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-2024 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 @NotNull private final ReadLock readLock; 097 098 // The Java lock that is used behind the scenes for all locking functionality. 099 @NotNull private final ReentrantReadWriteLock readWriteLock; 100 101 // The closeable write lock. 102 @NotNull 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 @NotNull() 144 public WriteLock lockWrite() 145 { 146 readWriteLock.writeLock().lock(); 147 return writeLock; 148 } 149 150 151 152 /** 153 * Acquires the write lock, blocking until the lock is available. 154 * 155 * @return The {@link WriteLock} instance that may be used to perform the 156 * unlock via the try-with-resources facility. 157 * 158 * @throws InterruptedException If the thread is interrupted while waiting 159 * to acquire the lock. 160 */ 161 @NotNull() 162 public WriteLock lockWriteInterruptibly() 163 throws InterruptedException 164 { 165 readWriteLock.writeLock().lockInterruptibly(); 166 return writeLock; 167 } 168 169 170 171 /** 172 * Tries to acquire the write lock, waiting up to the specified length of time 173 * for it to become available. 174 * 175 * @param waitTime The maximum length of time to wait for the lock. It must 176 * be greater than zero. 177 * @param timeUnit The time unit that should be used when evaluating the 178 * {@code waitTime} value. 179 * 180 * @return The {@link WriteLock} instance that may be used to perform the 181 * unlock via the try-with-resources facility. 182 * 183 * @throws InterruptedException If the thread is interrupted while waiting 184 * to acquire the lock. 185 * 186 * @throws TimeoutException If the lock could not be acquired within the 187 * specified length of time. 188 */ 189 @NotNull() 190 public WriteLock tryLockWrite(final long waitTime, 191 @NotNull final TimeUnit timeUnit) 192 throws InterruptedException, TimeoutException 193 { 194 if (waitTime <= 0) 195 { 196 Validator.violation( 197 "CloseableLock.tryLockWrite.waitTime must be greater than zero. " + 198 "The provided value was " + waitTime); 199 } 200 201 if (readWriteLock.writeLock().tryLock(waitTime, timeUnit)) 202 { 203 return writeLock; 204 } 205 else 206 { 207 throw new TimeoutException( 208 ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_WRITE_TIMEOUT.get( 209 StaticUtils.millisToHumanReadableDuration( 210 timeUnit.toMillis(waitTime)))); 211 } 212 } 213 214 215 216 /** 217 * Acquires a read lock, blocking until the lock is available. 218 * 219 * @return The {@link ReadLock} instance that may be used to perform the 220 * unlock via the try-with-resources facility. 221 */ 222 @NotNull() 223 public ReadLock lockRead() 224 { 225 readWriteLock.readLock().lock(); 226 return readLock; 227 } 228 229 230 231 /** 232 * Acquires a read lock, blocking until the lock is available. 233 * 234 * @return The {@link ReadLock} instance that may be used to perform the 235 * unlock via the try-with-resources facility. 236 * 237 * @throws InterruptedException If the thread is interrupted while waiting 238 * to acquire the lock. 239 */ 240 @NotNull() 241 public ReadLock lockReadInterruptibly() 242 throws InterruptedException 243 { 244 readWriteLock.readLock().lockInterruptibly(); 245 return readLock; 246 } 247 248 249 250 /** 251 * Tries to acquire a read lock, waiting up to the specified length of time 252 * for it to become available. 253 * 254 * @param waitTime The maximum length of time to wait for the lock. It must 255 * be greater than zero. 256 * @param timeUnit The time unit that should be used when evaluating the 257 * {@code waitTime} value. 258 * 259 * @return The {@link ReadLock} instance that may be used to perform the 260 * unlock via the try-with-resources facility. 261 * 262 * @throws InterruptedException If the thread is interrupted while waiting 263 * to acquire the lock. 264 * 265 * @throws TimeoutException If the lock could not be acquired within the 266 * specified length of time. 267 */ 268 @NotNull() 269 public ReadLock tryLockRead(final long waitTime, 270 @NotNull final TimeUnit timeUnit) 271 throws InterruptedException, TimeoutException 272 { 273 if (waitTime <= 0) 274 { 275 Validator.violation( 276 "CloseableLock.tryLockRead.waitTime must be greater than zero. " + 277 "The provided value was " + waitTime); 278 } 279 280 if (readWriteLock.readLock().tryLock(waitTime, timeUnit)) 281 { 282 return readLock; 283 } 284 else 285 { 286 throw new TimeoutException( 287 ERR_CLOSEABLE_RW_LOCK_TRY_LOCK_READ_TIMEOUT.get( 288 StaticUtils.millisToHumanReadableDuration( 289 timeUnit.toMillis(waitTime)))); 290 } 291 } 292 293 294 295 /** 296 * Indicates whether this lock uses fair ordering. 297 * 298 * @return {@code true} if this lock uses fair ordering, or {@code false} if 299 * not. 300 */ 301 public boolean isFair() 302 { 303 return readWriteLock.isFair(); 304 } 305 306 307 308 /** 309 * Indicates whether the write lock is currently held by any thread. 310 * 311 * @return {@code true} if the write lock is currently held by any thread, or 312 * {@code false} if not. 313 */ 314 public boolean isWriteLocked() 315 { 316 return readWriteLock.isWriteLocked(); 317 } 318 319 320 321 /** 322 * Indicates whether the write lock is currently held by the current thread. 323 * 324 * @return {@code true} if the write lock is currently held by the current 325 * thread, or {@code false} if not. 326 */ 327 public boolean isWriteLockedByCurrentThread() 328 { 329 return readWriteLock.isWriteLockedByCurrentThread(); 330 } 331 332 333 334 /** 335 * Retrieves the number of holds that the current thread has on the write 336 * lock. 337 * 338 * @return The number of holds that the current thread has on the write lock. 339 */ 340 public int getWriteHoldCount() 341 { 342 return readWriteLock.getWriteHoldCount(); 343 } 344 345 346 347 /** 348 * Retrieves the number of threads that currently hold the read lock. 349 * 350 * @return The number of threads that currently hold the read lock. 351 */ 352 public int getReadLockCount() 353 { 354 return readWriteLock.getReadLockCount(); 355 } 356 357 358 359 /** 360 * Retrieves the number of holds that the current thread has on the read lock. 361 * 362 * @return The number of holds that the current thread has on the read lock. 363 */ 364 public int getReadHoldCount() 365 { 366 return readWriteLock.getReadHoldCount(); 367 } 368 369 370 371 /** 372 * Indicates whether any threads are currently waiting to acquire either the 373 * write or read lock. 374 * 375 * @return {@code true} if any threads are currently waiting to acquire 376 * either the write or read lock, or {@code false} if not. 377 */ 378 public boolean hasQueuedThreads() 379 { 380 return readWriteLock.hasQueuedThreads(); 381 } 382 383 384 385 /** 386 * Indicates whether the specified thread is currently waiting to acquire 387 * either the write or read lock. 388 * 389 * @param thread The thread for which to make the determination. It must 390 * not be {@code null}. 391 * 392 * @return {@code true} if the specified thread is currently waiting to 393 * acquire either the write or read lock, or {@code false} if not. 394 */ 395 public boolean hasQueuedThread(@NotNull final Thread thread) 396 { 397 return readWriteLock.hasQueuedThread(thread); 398 } 399 400 401 402 /** 403 * Retrieves an estimate of the number of threads currently waiting to acquire 404 * either the write or read lock. 405 * 406 * @return An estimate of the number of threads currently waiting to acquire 407 * either the write or read lock. 408 */ 409 public int getQueueLength() 410 { 411 return readWriteLock.getQueueLength(); 412 } 413 414 415 416 /** 417 * Retrieves a string representation of this read-write lock. 418 * 419 * @return A string representation of this read-write lock. 420 */ 421 @Override() 422 @NotNull() 423 public String toString() 424 { 425 return "CloseableReadWriteLock(lock=" + readWriteLock.toString() + ')'; 426 } 427 428 429 430 /** 431 * This class provides a {@code Closeable} implementation that may be used to 432 * unlock a {@link CloseableReadWriteLock}'s read lock via Java's 433 * try-with-resources facility. 434 */ 435 public final class ReadLock 436 implements Closeable 437 { 438 // The associated read lock. 439 @NotNull private final ReentrantReadWriteLock.ReadLock lock; 440 441 442 443 /** 444 * Creates a new instance with the provided lock. 445 * 446 * @param lock The lock that will be unlocked when the [@link #close()} 447 * method is called. This must not be {@code null}. 448 */ 449 private ReadLock(@NotNull final ReentrantReadWriteLock.ReadLock lock) 450 { 451 this.lock = lock; 452 } 453 454 455 456 /** 457 * This method does nothing. However, calling it inside a try block when 458 * used in the try-with-resources framework can help avoid a compiler 459 * warning that the JVM will give you if you don't reference the 460 * {@code Closeable} object inside the try block. 461 */ 462 public void avoidCompilerWarning() 463 { 464 // No implementation is required. 465 } 466 467 468 469 /** 470 * Unlocks the associated lock. 471 */ 472 @Override() 473 public void close() 474 { 475 lock.unlock(); 476 } 477 } 478 479 480 481 /** 482 * This class provides a {@code Closeable} implementation that may be used to 483 * unlock a {@link CloseableReadWriteLock}'s write lock via Java's 484 * try-with-resources facility. 485 */ 486 public final class WriteLock 487 implements Closeable 488 { 489 // The associated read lock. 490 @NotNull private final ReentrantReadWriteLock.WriteLock lock; 491 492 493 494 /** 495 * Creates a new instance with the provided lock. 496 * 497 * @param lock The lock that will be unlocked when the [@link #close()} 498 * method is called. This must not be {@code null}. 499 */ 500 private WriteLock(@NotNull final ReentrantReadWriteLock.WriteLock lock) 501 { 502 this.lock = lock; 503 } 504 505 506 507 /** 508 * This method does nothing. However, calling it inside a try block when 509 * used in the try-with-resources framework can help avoid a compiler 510 * warning that the JVM will give you if you don't reference the 511 * {@code Closeable} object inside the try block. 512 */ 513 public void avoidCompilerWarning() 514 { 515 // No implementation is required. 516 } 517 518 519 520 /** 521 * Unlocks the associated lock. 522 */ 523 @Override() 524 public void close() 525 { 526 lock.unlock(); 527 } 528 } 529}