Backend Development 5 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Build a Custom Java Lock with AbstractQueuedSynchronizer

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:

Lock state diagram
Lock state diagram
Thread queue diagram
Thread queue diagram
AQS internal structure
AQS internal structure
Lock acquisition flow
Lock acquisition flow
JavaconcurrencyAbstractQueuedSynchronizercustom lock
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.