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.ReentrantLock; 029 030import static com.unboundid.util.UtilityMessages.*; 031 032 033 034/** 035 * This class provides an implementation of a reentrant lock that can be used 036 * with the Java try-with-resources facility. It does not implement the 037 * {@code java.util.concurrent.locks.Lock} interface in order to ensure that it 038 * can only be used through lock-with-resources mechanism, but it uses a 039 * {@code java.util.concurrent.locks.ReentrantLock} behind the scenes to provide 040 * its functionality. 041 * <BR><BR> 042 * <H2>Example</H2> 043 * The following example demonstrates how to use this lock using the Java 044 * try-with-resources facility: 045 * <PRE> 046 * // Wait for up to 5 seconds to acquire the lock. 047 * try (CloseableLock.Lock lock = 048 * closeableLock.tryLock(5L, TimeUnit.SECONDS)) 049 * { 050 * // NOTE: If you don't reference the lock object inside the try block, the 051 * // compiler will issue a warning. 052 * lock.avoidCompilerWarning(); 053 * 054 * // Do something while the lock is held. The lock will automatically be 055 * // released once code execution leaves this block. 056 * } 057 * catch (final InterruptedException e) 058 * { 059 * // The thread was interrupted before the lock could be acquired. 060 * } 061 * catch (final TimeoutException) 062 * { 063 * // The lock could not be acquired within the specified 5-second timeout. 064 * } 065 * </PRE> 066 */ 067@Mutable() 068@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 069public final class CloseableLock 070{ 071 // The {@code Closeable} object that will be returned by all of the methods 072 // used to acquire the lock. 073 private final Lock lock; 074 075 // The reentrant lock that will be used to actually perform the locking. 076 private final ReentrantLock reentrantLock; 077 078 079 080 /** 081 * Creates a new instance of this lock with a non-fair ordering policy. 082 */ 083 public CloseableLock() 084 { 085 this(false); 086 } 087 088 089 090 /** 091 * Creates a new instance of this lock with the specified ordering policy. 092 * 093 * @param fair Indicates whether the lock should use fair ordering. If 094 * {@code true}, then if multiple threads are waiting on the 095 * lock, then the one that has been waiting the longest is the 096 * one that will get it. If {@code false}, then no guarantee 097 * will be made about the order. Fair ordering can incur a 098 * performance penalty. 099 */ 100 public CloseableLock(final boolean fair) 101 { 102 reentrantLock = new ReentrantLock(fair); 103 lock = new Lock(reentrantLock); 104 } 105 106 107 108 /** 109 * Acquires this lock, blocking until the lock is available. 110 * 111 * @return The {@link Lock} instance that may be used to perform the 112 * unlock via the try-with-resources facility. 113 */ 114 public Lock lock() 115 { 116 reentrantLock.lock(); 117 return lock; 118 } 119 120 121 122 /** 123 * Acquires this lock, blocking until the lock is available. 124 * 125 * @return The {@link Lock} instance that may be used to perform the unlock 126 * via the try-with-resources facility. 127 * 128 * @throws InterruptedException If the thread is interrupted while waiting 129 * to acquire the lock. 130 */ 131 public Lock lockInterruptibly() 132 throws InterruptedException 133 { 134 reentrantLock.lockInterruptibly(); 135 return lock; 136 } 137 138 139 140 /** 141 * Tries to acquire the lock, waiting up to the specified length of time for 142 * it to become available. 143 * 144 * @param waitTime The maximum length of time to wait for the lock. It must 145 * be greater than zero. 146 * @param timeUnit The time unit that should be used when evaluating the 147 * {@code waitTime} value. 148 * 149 * @return The {@link Lock} instance that may be used to perform the unlock 150 * via the try-with-resources facility. 151 * 152 * @throws InterruptedException If the thread is interrupted while waiting 153 * to acquire the lock. 154 * 155 * @throws TimeoutException If the lock could not be acquired within the 156 * specified length of time. 157 */ 158 public Lock tryLock(final long waitTime, final TimeUnit timeUnit) 159 throws InterruptedException, TimeoutException 160 { 161 if (waitTime <= 0) 162 { 163 Validator.violation( 164 "CloseableLock.tryLock.waitTime must be greater than zero. The " + 165 "provided value was " + waitTime); 166 } 167 168 if (reentrantLock.tryLock(waitTime, timeUnit)) 169 { 170 return lock; 171 } 172 else 173 { 174 throw new TimeoutException(ERR_CLOSEABLE_LOCK_TRY_LOCK_TIMEOUT.get( 175 StaticUtils.millisToHumanReadableDuration( 176 timeUnit.toMillis(waitTime)))); 177 } 178 } 179 180 181 182 /** 183 * Indicates whether this lock uses fair ordering. 184 * 185 * @return {@code true} if this lock uses fair ordering, or {@code false} if 186 * not. 187 */ 188 public boolean isFair() 189 { 190 return reentrantLock.isFair(); 191 } 192 193 194 195 /** 196 * Indicates whether this lock is currently held by any thread. 197 * 198 * @return {@code true} if this lock is currently held by any thread, or 199 * {@code false} if not. 200 */ 201 public boolean isLocked() 202 { 203 return reentrantLock.isLocked(); 204 } 205 206 207 208 /** 209 * Indicates whether this lock is currently held by the current thread. 210 * 211 * @return {@code true} if this lock is currently held by the current thread, 212 * or {@code false} if not. 213 */ 214 public boolean isHeldByCurrentThread() 215 { 216 return reentrantLock.isHeldByCurrentThread(); 217 } 218 219 220 221 /** 222 * Retrieves the number of holds that the current thread has on the lock. 223 * 224 * @return The number of holds that the current thread has on the lock. 225 */ 226 public int getHoldCount() 227 { 228 return reentrantLock.getHoldCount(); 229 } 230 231 232 233 /** 234 * Indicates whether any threads are currently waiting to acquire this lock. 235 * 236 * @return {@code true} if any threads are currently waiting to acquire this 237 * lock, or {@code false} if not. 238 */ 239 public boolean hasQueuedThreads() 240 { 241 return reentrantLock.hasQueuedThreads(); 242 } 243 244 245 246 /** 247 * Indicates whether the specified thread is currently waiting to acquire this 248 * lock, or {@code false} if not. 249 * 250 * @param thread The thread for which to make the determination. It must 251 * not be {@code null}. 252 * 253 * @return {@code true} if the specified thread is currently waiting to 254 * acquire this lock, or {@code false} if not. 255 */ 256 public boolean hasQueuedThread(final Thread thread) 257 { 258 Validator.ensureNotNull(thread); 259 260 return reentrantLock.hasQueuedThread(thread); 261 } 262 263 264 265 /** 266 * Retrieves an estimate of the number of threads currently waiting to acquire 267 * this lock. 268 * 269 * @return An estimate of the number of threads currently waiting to acquire 270 * this lock. 271 */ 272 public int getQueueLength() 273 { 274 return reentrantLock.getQueueLength(); 275 } 276 277 278 279 /** 280 * Retrieves a string representation of this lock. 281 * 282 * @return A string representation of this lock. 283 */ 284 @Override() 285 public String toString() 286 { 287 return "CloseableLock(lock=" + reentrantLock.toString() + ')'; 288 } 289 290 291 292 /** 293 * This class provides a {@code Closeable} implementation that may be used to 294 * unlock a {@link CloseableLock} via Java's try-with-resources 295 * facility. 296 */ 297 public final class Lock 298 implements Closeable 299 { 300 // The associated reentrant lock. 301 private final ReentrantLock lock; 302 303 304 305 /** 306 * Creates a new instance with the provided lock. 307 * 308 * @param lock The lock that will be unlocked when the [@link #close()} 309 * method is called. This must not be {@code null}. 310 */ 311 private Lock(final ReentrantLock lock) 312 { 313 this.lock = lock; 314 } 315 316 317 318 /** 319 * This method does nothing. However, calling it inside a try block when 320 * used in the try-with-resources framework can help avoid a compiler 321 * warning that the JVM will give you if you don't reference the 322 * {@code Closeable} object inside the try block. 323 */ 324 public void avoidCompilerWarning() 325 { 326 // No implementation is required. 327 } 328 329 330 331 /** 332 * Unlocks the associated lock. 333 */ 334 @Override() 335 public void close() 336 { 337 lock.unlock(); 338 } 339 } 340}