Reentrant exclusive lock
What is reentry:
public void say(){ synchronized(this){ System.out.println("1"); synchronized(this){ //If it's not reentrant here, the agent is stuck here, which is equivalent to deadlock System.out.println("2"); } } }
Design exclusive lock
First, you need a state equal to 0, which means that no thread has acquired the lock. If it is greater than 0, it means that a thread has acquired the lock, and an error is reported when acquiring the lock again
If you want to support reentrant, you need to save the thread obtaining the lock. Next time the thread acquires the lock again, you can use state+1.
How to deal with lock acquisition by multiple threads
If thread A1 acquires a lock, what should thread A2 do if it also acquires a lock?
A2 finds that another thread has acquired the lock, so it needs to save A2, that is to say, blocking. After A1 releases the lock, A1 informs A2, and then A2 continues to acquire the lock.
If there are A3 and A4, we need to design a queue. The queue header, that is, the Head, always holds the lock and the queue is FIFO.
Exclusive lock AQS source code
public final void acquire(int arg) { if (!tryAcquire(arg) && //Attempt to acquire lock, subclass implementation, failed to acquire. Wrap the thread as Node and put it at the end of the queue. If there is no queue, create a new queue. head holds the lock by default acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){ selfInterrupt();//In the process of obtaining the lock, if it is interrupted, it is necessary to supplement the interruption } } final boolean acquireQueued(final Node node, int arg) { boolean failed = true; //Failed to queue or not try { boolean interrupted = false; //Whether it has been interrupted for (;;) { final Node p = node.predecessor(); //The predecessor node of the current node if (p == head && tryAcquire(arg)) { //The precursor node is head, which means the current node can try to acquire the lock setHead(node);//Get the lock, set the Head as the current node, because the current node holds the lock p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) &&//The current node does not acquire the lock. Set the precursor node to notify, and then block the current thread parkAndCheckInterrupt()) //Have you ever been interrupted interrupted = true; } } finally { if (failed) cancelAcquire(node);//Failed to acquire lock, delete current node } } public final boolean release(int arg) { //Release exclusive lock if (tryRelease(arg)) {//Subclass implementation Node h = head;//Head node holds lock if (h != null && h.waitStatus != 0)//There is a header node and the state is not initialized unparkSuccessor(h);//Change the state of the head node and call the thread of the successor node return true; } return false; }
Shared lock
Shared implementations have subclasses to implement, and the rest are basically the same as exclusive locks
AQS source code
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) //Subclass implementation, get failed, enter queue, block doAcquireShared(arg); } private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED);//Join end of team, mark as share boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor();//Precursor node if (p == head) { //The precursor node is the head node, so it attempts to acquire the lock int r = tryAcquireShared(arg); if (r >= 0) {//The total number of shared locks is required. If it is greater than or equal to 0, it means that other threads can be woken up setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) &&//Same as exclusive lock logic parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node); /* * Try to signal next queued node if: * Propagation was indicated by caller, * or was recorded (as h.waitStatus either before * or after setHead) by a previous operation * (note: this uses sign-check of waitStatus because * PROPAGATE status may transition to SIGNAL.) * and * The next node is waiting in shared mode, * or we don't know, because it appears null * * The conservatism in both of these checks may cause * unnecessary wake-ups, but only when there are multiple * racing acquires/releases, so most need signals now or soon * anyway. */ if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared();//If the next node is shared } }
Condition
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); //Queue the current thread int savedState = fullyRelease(node); //Release the lock held by the current thread int interruptMode = 0; while (!isOnSyncQueue(node)) { //It's useless here. Only when two threads are concurrent can TRUE be returned LockSupport.park(this);//block if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }