Concept:
ReentrantLock is a reentrant lock that supports fair locks and unfair locks. The underlying implementation is based on AQS.
Usage scenarios:
It is more flexible and lightweight to use than Synchronized.
ReentrantLock | Synchronized | |
Usage location | Code block | Method, code block |
Implementation mechanism | AQS | Monitor (monitor) |
Release form | Explicit call, explicit release | Automatic release |
Lock type | Fair lock, unfair lock | Unfair lock |
Reentrancy | Reentrancy | Reentrancy |
Basic usage:
/** * @author maoyouhua * @version jdk21 */ public class AqsDemo { private final ReentrantLock lock = new ReentrantLock(); public void write1(){ System.out.println("Enter write1..."); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "write1 ... aqs"); TimeUnit.SECONDS.sleep(10 * 60); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); System.out.println("Release write1..."); } } public void write2(){ System.out.println("Enter write2..."); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "write2 ... aqs"); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); System.out.println("Release write2..."); } } public static void main(String[] args) { AqsDemo aqsDemo = new AqsDemo(); new Thread(aqsDemo::write1,"Thread - write - 1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(aqsDemo::write2,"Thread - write - 2").start(); } }
Source code analysis:
ReentrantLock implements the Lock interface and has two important methods lock() and unlock();
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
The constructor supports the creation of fair locks and unfair locks. The default is unfair locks. Unfair locks can reduce resources wasted by thread switching and help improve execution efficiency.
It can be seen that FairSync and NonfairSync inherit from Sync and implement logic based on AbstractQueuedSynchronizer.
AbstractQueuedSynchronizer internally maintains a CLH Nodes queue and lock status flag state to complete collaboration.
/** * @author maoyouhua * @version jdk21 */ //Lock status flag bit private volatile int state; // CLH Nodes abstract static class Node { volatile Node prev; // initially attached via casTail volatile Node next; // visibly nonnull when signallable Thread waiter; // visibly nonnull when enqueued volatile int status; // written by owner, atomic bit ops by others // methods for atomic operations final boolean casPrev(Node c, Node v) { // for cleanQueue return U.weakCompareAndSetReference(this, PREV, c, v); } final boolean casNext(Node c, Node v) { // for cleanQueue return U.weakCompareAndSetReference(this, NEXT, c, v); } final int getAndUnsetStatus(int v) { // for signaling return U.getAndBitwiseAndInt(this, STATUS, ~v); } final void setPrevRelaxed(Node p) { // for off-queue assignment U.putReference(this, PREV, p); } final void setStatusRelaxed(int s) { // for off-queue assignment U.putInt(this, STATUS, s); } final void clearStatus() { // for reducing unneeded signals U.putIntOpaque(this, STATUS, 0); } private static final long STATUS = U.objectFieldOffset(Node.class, "status"); private static final long NEXT = U.objectFieldOffset(Node.class, "next"); private static final long PREV = U.objectFieldOffset(Node.class, "prev"); }
lock():
/** * @author maoyouhua * @version jdk21 */ // ReentrantLock public void lock() { //The implementation of the lock method is the lock method of the inner class Sync sync.lock(); } // Sync final void lock() { //Initial attempt to acquire the lock if (!initialTryLock()) acquire(1); }
/** * @author maoyouhua * @version jdk21 */ // NonfairSync final boolean initialTryLock() { Thread current = Thread.currentThread(); // When acquiring the lock for the first time, directly set the lock status to 1 and set the occupying thread to the current thread. if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } // If the value exchange fails, determine whether the current thread is the occupying thread. If so, add one to the status value. //Here is the key to the reentrant lock else if (getExclusiveOwnerThread() == current) { int c = getState() + 1; if (c < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(c); return true; // None of the previous conditions are met and return false } else return false; }
/** * @author maoyouhua * @version jdk21 */ // FairSync final boolean initialTryLock() { Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //Fair lock needs to be queued if (!hasQueuedThreads() & amp; & amp; compareAndSetState(0, 1)) { setExclusiveOwnerThread(current); return true; } } else if (getExclusiveOwnerThread() == current) { if ( + + c < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(c); return true; } return false; }
/** * @author maoyouhua * @version jdk21 */ // queue public final boolean hasQueuedThreads() { for (Node p = tail, h = head; p != h & amp; & amp; p != null; p = p.prev) if (p.status >= 0) return true; return false; }
/** * @author maoyouhua * @version jdk21 */ // AbstractQueuedSynchronizer public final void acquire(int arg) { //Try to acquire the lock again if (!tryAcquire(arg)) // If acquisition fails, go to acquire acquire(null, arg, false, false, false, 0L); }
/** * @author maoyouhua * @version jdk21 */ // NonfairSync protected final boolean tryAcquire(int acquires) { //Acquire the lock again. The lock status is 0, which means the lock is not occupied. Use CAS to acquire the lock again, and set the occupying thread to the current thread. if (getState() == 0 & amp; & amp; compareAndSetState(0, acquires)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } //return false if acquisition fails return false; }
/** * @author maoyouhua * @version jdk21 */ // AbstractQueuedSynchronizer final int acquire(Node node, int arg, boolean shared, boolean interruptible, boolean timed, long time) { Thread current = Thread.currentThread(); byte spins = 0, postSpins = 0; // retries upon unpark of first thread boolean interrupted = false, first = false; Node pred = null; // predecessor of node when enqueued /* * Repeatedly: * Check if node now first * if so, ensure head stable, else ensure valid predecessor * if node is first or not yet enqueued, try acquiring * else if queue is not initialized, do so by attaching new header node * resort to spinwait on OOME trying to create node * else if node not yet created, create it * resort to spinwait on OOME trying to create node * else if not yet enqueued, try once to enqueue * else if woken from park, retry (up to postSpins times) * else if WAITING status not set, set and retry * else park and clear WAITING status, and check cancellation */ for (;;) { if (!first & amp; & amp; (pred = (node == null) ? null : node.prev) != null & amp; & amp; !(first = (head == pred))) { if (pred.status < 0) { cleanQueue(); // predecessor canceled continue; } else if (pred.prev == null) { Thread.onSpinWait(); // ensure serialization continue; } } if (first || pred == null) { // 1. Try tryAcquire // 3. Try tryAcquire again // 5. Try again tryAcquire // 7. Try again tryAcquire // 9. Try again tryAcquire again // 11. Try tryAcquire again and again, and obtain the lock successfully. boolean acquired; try { if (shared) acquired = (tryAcquireShared(arg) >= 0); else acquired = tryAcquire(arg); } catch (Throwable ex) { cancelAcquire(node, interrupted, false); throw ex; } // 12. Clean up the front node, set the current node as the head node, and set the sentinel node if (acquired) { if (first) { node.prev = null; head = node; pred.next = null; node.waiter = null; if (shared) signalNextIfShared(node); if (interrupted) current.interrupt(); } return 1; } } Node t; // 2. If there is no tail node, initialize the head and tail nodes, which are called sentinel nodes. if ((t = tail) == null) { // initialize queue if (tryInitializeHead() == null) return acquireOnOOME(shared, arg); // 4. If the node is null, create a node of SharedNode type. } else if (node == null) { // allocate; retry before enqueue try { node = (shared) ? new SharedNode() : new ExclusiveNode(); } catch (OutOfMemoryError oome) { return acquireOnOOME(shared, arg); } // 6. The front node is empty, ready to join the queue, and set the front node and thread } else if (pred == null) { // try to enqueue node.waiter = current; node.setPrevRelaxed(t); // avoid unnecessary fence if (!casTail(t, node)) node.setPrevRelaxed(null); // back out else t.next = node; } else if (first & amp; & amp; spins != 0) { --spins; // reduce unfairness on rewaits Thread.onSpinWait(); // 8. Set the current thread status to WATTING = 1 } else if (node.status == 0) { node.status = WAITING; // enable signal and recheck } else { long nanos; spins = postSpins = (byte)((postSpins << 1) | 1); if (!timed) // 10. Block the current thread and release the pass after obtaining the pass. LockSupport.park(this); else if ((nanos = time - System.nanoTime()) > 0L) LockSupport.parkNanos(this, nanos); else break; node.clearStatus(); if ((interrupted |= Thread.interrupted()) & amp; & amp; interruptible) break; } } return cancelAcquire(node, interrupted, interruptible); }
/** * @author maoyouhua * @version jdk21 */ // AbstractQueuedSynchronizer private Node tryInitializeHead() { for (Node h = null, t;;) { if ((t = tail) != null) return t; else if (head != null) Thread.onSpinWait(); else { if (h == null) { try { h = new ExclusiveNode(); } catch (OutOfMemoryError oome) { return null; } } if (U.compareAndSetReference(this, HEAD, null, h)) return tail = h; } } }
/** * @author maoyouhua * @version jdk21 */ // AbstractQueuedSynchronizer //Cancel lock acquisition private int cancelAcquire(Node node, boolean interrupted, boolean interruptible) { if (node != null) { node.waiter = null; node.status = CANCELLED; if (node.prev != null) cleanQueue(); } if (interrupted) { if(interruptible) return CANCELLED; else Thread.currentThread().interrupt(); } return 0; }
unlock():
/** * @author maoyouhua * @version jdk21 */ // ReentrantLock public void unlock() { sync.release(1); } // AbstractQueuedSynchronizer public final boolean release(int arg) { if (tryRelease(arg)) { signalNext(head); return true; } return false; }
/** * @author maoyouhua * @version jdk21 */ // ReentrantLock //Clear the thread holding the lock, and set the lock status value minus one protected final boolean tryRelease(int releases) { int c = getState() - releases; if (getExclusiveOwnerThread() != Thread.currentThread()) throw new IllegalMonitorStateException(); boolean free = (c == 0); if (free) setExclusiveOwnerThread(null); setState(c); return free; }
/** * @author maoyouhua * @version jdk21 */ //Give the next queued thread a pass private static void signalNext(Node h) { Node s; if (h != null & amp; & amp; (s = h.next) != null & amp; & amp; s.status != 0) { s.getAndUnsetStatus(WAITING); LockSupport.unpark(s.waiter); } }
The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 138932 people are learning the system