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.

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(); }
}

These methods are the core for acquiring and releasing locks. The following example shows a custom lock implementation using AQS.

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; }
}

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
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAbstractQueuedSynchronizercustom 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

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.