io.github.zon_g.ABC.client.locks.Lock Maven / Gradle / Ivy
/**
* Copyright 2021 the original author, Lin Tang
*
* 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 io.github.zon_g.ABC.client.locks;
import io.github.zon_g.ABC.commons.types.LockType;
/**
* {@link Lock} implementations provides more extensive locking operations for distributed
* application, where synchronized
methods or other {@link java.util.concurrent.locks.Lock}
* implementations can not provide locking operation. They allow more flexible structuring,
* having quite different properties.
*
* Generally, a lock is a tool for controlling access to a shared resource by multiple threads.
* Nevertheless, a resource can be shared by threads from multiple application in distributed
* applications. Commonly, a lock provides exclusive access to a shared resource: only one thread
* at a time can acquire the lock and all access to the shared resource requires that the lock be
* acquired first. However, some locks may allow concurrent access to a shared resource, such as
* the read lock of a {@link ReadWriteLock}.
*
* With this increased flexibility comes additional responsibility. In most cases, the following
* idiom should be used:
*
*
* {@code
* Lock l = ...;
* try {
* // access the resource protected by this lock
* } finally {
* l.unlock();
* }}
*
*
* Here, locking and unlocking are encouraged to appear in the same scope. When locking and unlocking
* occur in different scopes, care must be taken to ensure that all code that is executed while the lock
* is held is protected by try-finally or try-catch to ensure that the lock is released when necessary.
*
* {@link Lock} implementations provide additional functionality by providing a non-blocking attempt to
* acquire a lock ({@link #tryLock()}), an expiration for a lock if locking succeeds
* ({@link TimeoutLock}) and reentrant usage ({@link ReentrantLock}).
*
* A {@link Lock} class can also provides behavior and semantics that is quite different from that of the
* implicit monitor lock, such as reentrant usage.
*
* Note that {@link Lock} instances are just normal objects and can themselves be used as the target in
* a synchronized
statement. Acquiring the monitor lock of a {@link Lock} instance has no
* specified relationship with invoking any of the {@link #lock()} methods of that instance. Thus it is
* recommended that to avoid confusion you never use Lock
instances in this way strongly.
*
* In a distributed application, it is mandatory to specify the lock name (or lock key {@link #lockKey})
* to distinguish distributed locks with the same lock type
* {@link LockType} for locking and unlocking.
*
* Implementations of {@link Lock} never throw an exception when locking or unlocking fails. But it is
* still strongly recommended that locking and unlocking should be used in a try-catch or
* try-finally structure, avoiding lock is held forever in lock server incurred by exceptions
* thrown from code access the resource protected by {@code Lock} instances. Even a lock is an instance
* of {@link TimeoutLock}, it is recommended to do so.
*
* Besides, {@link Lock} provides no interruption, which implies that locking can be canceled.
*
* @author Lin Tang
* @see SimpleLock
* @see ReentrantLock
* @see TimeoutLock
* @see ReadWriteLock
* @since 1.0.0
*/
public abstract class Lock {
protected static final String EMPTY_LOCK_KEY = "Lock key should not be null or empty, reset lock key.",
LOCKING_ALREADY = "Locking succeeds already, lock cancels.",
LOCKING_FAILS = "Locking fails before, unlock cancels.",
UNLOCKING_ALREADY = "Unlocking succeeds already, unlock cancels.";
/**
* Key to distinguish locks.
*/
private final String lockKey;
private boolean success = false;
private boolean canLock = true;
private boolean canUnlock = false;
Lock(String lockKey) {
this.lockKey = lockKey;
}
/**
* Acquires the lock if and only if it is free at the time of invocation.
*
* Acquires the lock if it is available and returns with the value true
immediately.
* If the lock is not available then this method will return immediately with the value false. Namely,
* {@link #tryLock()} only sends a lock request to lock server once and acquires a lock response
* immediately, representing whether locking succeeds or not.
*
* A typical usage idiom for this method would be:
*
* {@code
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // manipulate protected state
* } finally {
* lock.unlock();
* }
* } else {
* // perform alternative actions
* }}
*
*
* This usage ensures that the lock is unlocked if it was acquired, and does not
* try to unlock if the lock was not acquired.
*
* Implementation Considerations
*
* Only an initialized lock instance, not support for reentrant lock, can invoke {@link #tryLock()},
* except for {@link ReentrantLock}. Once a lock instance succeeds in locking, lock request should
* be intercepted and returns immediately.
*
*
* @return {@code true} if the lock was acquired and {@code false} otherwise.
*/
protected abstract boolean tryLock();
/**
* Acquires the lock.
*
* If the lock is not available then current thread will wait until the lock is available at lock server,
* which implies the lock has been acquired. Namely, {@link #lock()} locking never fails.
*
* Implementation Considerations
*
* Only an initialized lock instance, not support for reentrant lock, can invoke {@link #lock()},
* except for {@link ReentrantLock}. Once a lock instance succeeds in locking, lock request should
* be intercepted and returns immediately.
*
* No support for interruption
*/
protected abstract void lock();
/**
* Releases the lock.
*
* Implementation Considerations
*
* A Lock
implementation will usually impose restrictions on which thread can release a
* lock (typically only the holder of the lock, which has not been unlocked or unlocked completely
* before, can invoke {@link #unlock()}). It throws no exceptions as much as possible.
*/
protected abstract void unlock();
/**
* Validates whether lock key is available.
*
* @return true
if lock key is available and false
otherwise.
*/
boolean validateLockKey() {
return lockKey != null && lockKey.length() > 0;
}
String getLockKey() {
return this.lockKey;
}
boolean isSuccess() {
return this.success;
}
void setSuccess(boolean success) {
this.success = success;
}
boolean canLock() {
return this.canLock;
}
void setCanLock(boolean canLock) {
this.canLock = canLock;
}
boolean canUnlock() {
return this.canUnlock;
}
void setCanUnlock(boolean canUnlock) {
this.canUnlock = canUnlock;
}
}