How to Build a Custom Java Lock with AbstractQueuedSynchronizer
This article explains how to extend AbstractQueuedSynchronizer to create a custom lock in Java, detailing the core AQS methods, the implementation of a PLock class, and the handling of exclusive and shared acquisition and release logic.
By extending AbstractQueuedSynchronizer (AQS), you can implement the core logic of a lock; AQS maintains an int state field to represent the lock state.
The AQS class provides several protected methods that subclasses must implement, such as tryAcquire, tryAcquireShared, tryRelease, tryReleaseShared, and isHeldExclusively.
<code>public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
// Exclusive mode: try to acquire the resource, return true on success, false on failure
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
// Shared mode: try to acquire the resource, return negative on failure, zero on success with no remaining permits, positive on success with remaining permits
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
// Exclusive mode: try to release the resource, return true on success
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
// Shared mode: try to release the resource, return true if subsequent nodes may be awakened
protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }
// Whether the current thread holds the lock exclusively (used by conditions)
protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
}</code>These methods are the core for acquiring and releasing locks. The following example shows a custom lock implementation using AQS.
<code>public class PLock implements Lock {
private final Sync sync;
public PLock() {
sync = new Sync();
}
private class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1L;
public void lock() {
// compareAndSetState updates the state via CAS
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
@Override
protected boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
@Override
protected boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
@Override
protected boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
}
@Override
public void lock() { sync.lock(); }
@Override
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
@Override
public boolean tryLock() { return sync.tryAcquire(1); }
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() { sync.release(1); }
@Override
public Condition newCondition() { return null; }
}
</code>Illustrations of the lock state transitions and queue management are shown below:
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.