How do I write custom synchronizers using AbstractQueuedSynchronizer?

Writing custom synchronizers with AbstractQueuedSynchronizer (AQS) in Java involves creating classes that encapsulate synchronization logic, such as locks, semaphores, or barriers. AQS simplifies the development of concurrency tools by handling queueing and thread state management. Below are the key steps and guidelines to write custom synchronizers using AbstractQueuedSynchronizer:

1. Understand AQS Concepts

  • state: AQS maintains a simple int value (state) to represent the synchronization state. The meaning of state varies depending on the synchronizer you create (e.g., lock held count for a reentrant lock, permits for a semaphore, etc.).
  • Exclusive Mode: Only one thread can hold the resource (e.g., ReentrantLock).
  • Shared Mode: Multiple threads can share the resource (e.g., Semaphore, CountDownLatch).

2. Create a Custom Synchronizer Class

Your custom synchronizer class will generally:

  • Extend AbstractQueuedSynchronizer.
  • Use the state variable to model your synchronization logic.
  • Implement key methods such as tryAcquire, tryRelease, tryAcquireShared, and tryReleaseShared based on whether you’re implementing exclusive or shared behavior.

3. Implement Required Methods

For Exclusive Mode:

Override:

  • tryAcquire(int arg): Define the logic to acquire the resource exclusively. Return true if the acquisition is successful, otherwise return false.
  • tryRelease(int arg): Define the logic to release the resource. Return true if the state transition occurs and allows waiting threads to proceed.

For Shared Mode:

Override:

  • tryAcquireShared(int arg): Define the logic to acquire the resource in shared mode. Return:
    • Negative if the acquisition fails.
    • Zero if no more shared acquisitions are allowed.
    • Positive if the acquisition is successful, and more threads can share the resource.
  • tryReleaseShared(int arg): Define the logic to release the resource in shared mode. Usually, decrement the state and decide if more threads can proceed.

4. Publish the Synchronizer to Clients

AQS is always used as part of a larger custom synchronization object. Expose public methods in your custom class to wrap the AQS functionality. For instance:

  • For exclusive locks: lock and unlock methods.
  • For shared locks: Methods such as acquireShared and releaseShared.

5. Example Implementations

Example 1: Simple Mutex (ReentrantLock Equivalent)

Code for an exclusive synchronizer:

package org.kodejava.util.concurrent;

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class SimpleMutex {
    // Custom synchronizer extending AQS
    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int ignored) {
            // Attempt to set state to 1 if it's currently 0 (lock is free)
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int ignored) {
            // Only lock owner can release
            if (getState() == 0 || getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0); // Free the lock
            return true; // Allow further attempts to acquire the lock
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1 && getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

Example 2: Simple Semaphore (Shared Synchronizer)

Code demonstrating shared mode:

package org.kodejava.util.concurrent;

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class SimpleSemaphore {
    private static class Sync extends AbstractQueuedSynchronizer {
        Sync(int permits) {
            setState(permits);
        }

        @Override
        protected int tryAcquireShared(int permits) {
            for (; ; ) {
                int current = getState();
                int remaining = current - permits;
                // Check if we can acquire the permits
                if (remaining < 0 || compareAndSetState(current, remaining)) {
                    return remaining;
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int permits) {
            for (; ; ) {
                int current = getState();
                int next = current + permits;
                // Release the permits
                if (compareAndSetState(current, next)) {
                    return true;
                }
            }
        }
    }

    private final Sync sync;

    public SimpleSemaphore(int permits) {
        sync = new Sync(permits);
    }

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public void release() {
        sync.releaseShared(1);
    }
}

6. Testing and Validation

  • Test your custom synchronizer in multithreaded environments to ensure correctness.
  • Use proper test tools like JUnit or TestNG.
  • Validate edge cases, such as reentrancy (if applicable), releasing resources by non-owners, or negative state transitions.

Best Practices

  • Always ensure a clean state transition in synchronization methods.
  • Use atomic operations to modify state (e.g., compareAndSetState).
  • Avoid busy spinning (e.g., Thread.yield() or blocking mechanisms are better).
  • Use AQS’s built-in blocking mechanisms like acquire, acquireShared, release, or releaseShared.

By following these steps and practices, you can create robust custom synchronizers tailored to your concurrency requirements.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.