Implementing a Custom Java AQS Lock from Scratch
This tutorial walks through building a simple Java lock using AbstractQueuedSynchronizer (AQS), starting with a basic MyLock class, adding thread parking with Unsafe, creating a FIFO thread container, and refining lock and unlock methods to achieve correct synchronization and avoid lock starvation.
AbstractQueuedSynchronizer (AQS) is the core of Java's concurrency utilities. The article guides readers to implement a minimal lock by first defining a MyLock class with empty lock() and unlock() methods, then writing a test class that spawns multiple threads to exercise the lock.
public class MyLock {
public void lock() {}
public void unlock() {}
}Running the test shows interleaved thread output, indicating the lock does not work. To fix this, the tutorial introduces Unsafe methods such as compareAndSwapInt and park / unpark for atomic state changes and thread parking.
public final native boolean compareAndSwapInt(Object obj, long offset, int expected, int val);
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);A static helper class UNSAFE obtains the singleton Unsafe instance via reflection and caches field offsets for later use.
public class UNSAFE {
public static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (Exception e) { e.printStackTrace(); }
}
}Because a simple ConcurrentHashMap does not preserve FIFO order, a custom linked‑list container MyThreadContainer is created. It holds a volatile head and tail of Node objects, each storing a thread reference and links to previous/next nodes.
public class MyThreadContainer {
volatile Node head;
volatile Node tail;
private static long headOffset = 0L;
private static long tailOffset = 0L;
static {
try {
headOffset = UNSAFE.unsafe.objectFieldOffset(MyThreadContainer.class.getDeclaredField("head"));
tailOffset = UNSAFE.unsafe.objectFieldOffset(MyThreadContainer.class.getDeclaredField("tail"));
} catch (NoSuchFieldException e) { e.printStackTrace(); }
}
private boolean compareAndSetHead(Node node) {
return UNSAFE.unsafe.compareAndSwapObject(this, headOffset, null, node);
}
private boolean compareAndSetTail(Node expect, Node node) {
return UNSAFE.unsafe.compareAndSwapObject(this, tailOffset, expect, node);
}
class Node {
volatile Thread thread;
volatile Node prev;
volatile Node next;
Node(Thread t) { this.thread = t; }
}
public Node addWaiter() {
Node node = new Node(Thread.currentThread());
while (true) {
Node pred = tail;
if (pred != null) {
if (compareAndSetTail(pred, node)) {
node.prev = pred;
pred.next = node;
return node;
}
} else {
if (compareAndSetHead(node)) {
tail = node;
return node;
}
}
}
}
}The MyLock.lock() method now attempts an atomic state transition; if it fails, it enqueues the current thread via threadContainer.addWaiter() and parks the thread.
public void lock() {
while (true) {
if (UNSAFE.unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {
System.out.println("Thread " + Thread.currentThread().getName() + ", acquired lock");
break;
} else {
threadContainer.addWaiter();
UNSAFE.unsafe.park(false, 0L);
}
}
}The unlock() method resets the state and unparks the first waiting thread, also fixing the linked list pointers to maintain FIFO order.
public void unlock() {
UNSAFE.unsafe.compareAndSwapInt(this, stateOffset, 1, 0);
if (head != null) {
Node oldHead = head;
Node newHead = oldHead.next;
head = newHead;
oldHead.next = null;
UNSAFE.unsafe.unpark(oldHead.thread);
if (newHead != null) newHead.prev = null;
}
}Running the updated test shows that only one thread holds the lock at a time, and waiting threads acquire the lock in the order they were parked, confirming a correct FIFO lock implementation.
Hujiang Technology
We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.
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.