/* * Copyright (C) 2018-2020 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.util; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.WeakHashMap; import java.util.concurrent.locks.ReentrantLock; /** * A {@link ReentrantLock} lock that can be unlocked using an {@link AutoCloseable}. This allows for easy locking of * resources, using a specific class as a namespace. Typical usage: *
 *     try (final AutoCloseableReentrantLock.AutoCloseableLock ignored = new AutoCloseableReentrantLock(Clazz.class, user.getUsername()).lock()) {
 *         user.performNonThreadSafeTask();
 *     }
 * 
*

* This essentially has the same effect as: *

 *     synchronised ((Clazz.class.getName() + user.getUsername()).intern()) {
 *         user.performNonThreadSafeTask();
 *     }
 * 
*

* but has advantages in that the current status of the lock can interrogated, the lock can be acquired interruptibly, etc. */ public class AutoCloseableReentrantLock { // This is a WeakHashMap - when there are no references to the key, the entry will be removed private static final Map LOCK_MAP = Collections.synchronizedMap(new WeakHashMap<>()); private final ReentrantLock lock; private final AutoCloseableLock autoCloseable; private String key; /** * Create a class and resource specific lock. If another thread has not closed another AutoCloseableReentrantLock * with the same class and resource then this will block until it is closed. * * @param clazz The class for which the lock should be created. * @param resource The resource for which the lock should be created. */ public AutoCloseableReentrantLock(final Class clazz, final String resource) { key = (clazz.getName() + '#' + resource).intern(); lock = LOCK_MAP.computeIfAbsent(key, missingKey -> new ReentrantLock()); autoCloseable = new AutoCloseableLock(this); } private synchronized void close() throws IllegalMonitorStateException { lock.unlock(); // Clear the reference to the key so the GC can remove the entry from the WeakHashMap if no-one else has it if (!lock.isHeldByCurrentThread()) { key = null; } } private synchronized void checkNotReleased() throws IllegalStateException { if (key == null) { throw new IllegalStateException("Lock has already been released"); } } /** * Acquires the lock, blocking indefinitely. * * @return An AutoCloseableLock * @throws IllegalStateException if this lock has already been released by the last thread to hold it */ @SuppressWarnings( "LockAcquiredButNotSafelyReleased" ) public AutoCloseableLock lock() throws IllegalStateException { checkNotReleased(); lock.lock(); return autoCloseable; } /** * Tries to acquire the lock, returning immediately. * * @return An AutoCloseableLock if the lock was required, otherwise empty. * @throws IllegalStateException if this lock has already been released by the last thread to hold it */ public Optional tryLock() { checkNotReleased(); if (lock.tryLock()) { return Optional.of(autoCloseable); } else { return Optional.empty(); } } /** * Acquires the lock, blocking until the lock is acquired or the thread is interrupted. * * @return An AutoCloseableLock * @throws InterruptedException if the thread was interrupted before the lock could be acquired * @throws IllegalStateException if this lock has already been released by the last thread to hold it */ @SuppressWarnings( "LockAcquiredButNotSafelyReleased" ) public AutoCloseableLock lockInterruptibly() throws InterruptedException, IllegalStateException { checkNotReleased(); lock.lockInterruptibly(); return autoCloseable; } /** * Queries if this lock is held by the current thread. * * @return {@code true} if current thread holds this lock and {@code false} otherwise * @see ReentrantLock#isHeldByCurrentThread() */ public boolean isHeldByCurrentThread() { return lock.isHeldByCurrentThread(); } /** * Queries if this lock is held by any thread. This method is * designed for use in monitoring of the system state, * not for synchronization control. * * @return {@code true} if any thread holds this lock and {@code false} otherwise * @see ReentrantLock#isLocked() */ public boolean isLocked() { return lock.isLocked(); } public static final class AutoCloseableLock implements AutoCloseable { private final AutoCloseableReentrantLock lock; private AutoCloseableLock(final AutoCloseableReentrantLock lock) { this.lock = lock; } /** * Releases the lock. * * @throws IllegalMonitorStateException if the current thread does not hold the lock. */ @Override public void close() throws IllegalMonitorStateException { lock.close(); } } }