1 introduction

A lock is used to control how multiple threads can access a shared resource. Generally, a lock can prevent multiple threads from accessing a shared resource at the same time (however, some locks can allow multiple threads to access a shared resource concurrently, such as read/write locks). In the past, Java programs rely on synchronized to achieve the Lock function, but after Java SE 5, and the package is added to the Lock interface (and related implementation classes) to achieve the Lock function, he provides a synchronization function similar to the synchronized keyword, It only needs explicit lock acquisition and lock release when used. Although it lacks the convenience of implicit lock acquisition and lock release provided by synchronized, it has the operability of lock acquisition and lock release, interruptable lock acquisition and timeout lock acquisition and other synchronization features that synchronized keywords do not have. Many locks are implemented through the implementation of Lock interface to complete the operation of the Lock, such as ReentrantLock, Redisson distributed Lock, and so on. The implementation of Lock interface is basically through the aggregation of a subclass of synchronizer to complete the thread access control, and synchronizer, Is what we call the AQS (AbstractQueuedSynchronizer), is to record the contents of today.

2 What is AQS

AQS (queue synchronizer AbstractQueuedSynchronizer) is used to construct the lock or other synchronous component based framework, it USES a int member variables to represent the synchronization state, through the built-in FIFO queue to complete resources for thread line work.

As shown in the figure above, the main use of the synchronizer is inheritance. Subclasses manage synchronization state by inheriting the synchronizer and implementing its abstract method. In the implementation of the abstract method, synchronization state must be modified.

1. GetState (): obtains the current synchronization status

2. SetState (): Sets the current synchronization state

CompareAndSetState (int expect, int update): CAS is used to set the current state. This method can ensure the atomicity of the state setting

The subclass is recommended to be defined as a static inner class of a custom synchronization component. The synchronizer itself does not implement any synchronization interface, but simply defines methods for obtaining and releasing synchronization state for use by the custom synchronization component. Synchronizers can support both exclusive (a single thread acquires the lock) and shared (multiple threads acquire the lock), making it easy to implement different types of synchronization components (ReentrantLock, ReentrantLock, read/write lock, etc.). ReentrantReadWriteLock, counters: CountDownLatch, and more)

3 Methods that synchronizer can override

The following code is the method to override after reentrant lock inherits synchronizer:

  • Protected Boolean tryAcquire(int acquires): Obtain the lock status exclusively. To implement this method, query the current status and determine whether the synchronization status meets the expectation. Then set the synchronization status by CAS.
/** * NonfairSync extends Sync {private static Final Long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final Boolean tryAcquire(int acquires) {// nonfairTryAcquire (int acquires) {return nonfairTryAcquire (int acquires) {return nonfairTryAcquire (int acquires) nonfairTryAcquire(acquires); }} /** * FairSync extends Sync {private static Final Long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); Int c = getState(); If (c == 0) {if (c == 0) {if (! hasQueuedPredecessors() && 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; }}Copy the code

  • Protected Boolean tryRelease(int acquires): Exclusive release of the lock state. Threads waiting to acquire the lock state will have the opportunity to acquire the lock state.
protected final 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; }Copy the code

  • Protected Boolean isHeldExclusively(): Specifies whether the current synchronizer is held by the thread in exclusive mode. This method usually indicates whether the current synchronizer is held by the thread lock.
protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
Copy the code

The following code is overridden after the read/write lock inherits the synchronizer:

  • Protected int tryAcquireShared(int arg): Shared get synchronization status, return a value greater than 0, the lock is obtained successfully, otherwise failed.
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); Int c = getState(); // Return -1 if (exclusiveCount(c)! = 0 && getExclusiveOwnerThread() ! = current) return -1; Int r = sharedCount(c); // Check whether the CAS is set successfully if (! readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, C + SHARED_UNIT)) {if (r == 0) {firstReader = current; firstReaderHoldCount = 1; Else if (firstReader == current) {firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid ! = getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } // Return fullTryAcquireShared(current); }Copy the code

  • Protected Boolean tryReleaseShared(int arG): Shared release synchronization status.
protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); If (firstReader == current) {// Assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid ! = getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1) { readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } --rh.count; } for (;;) { int c = getState(); int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; }}Copy the code

The above example is divided into two examples, because read-write locks are shared locks and reentrant locks are exclusive locks, and the synchronizer provides rewritable methods for both shared locks and exclusive locks to acquire or release locks.

4 Template method provided by synchronizer

  • Void acquire(int arg): Exclusive access to the synchronization status, if the current thread successfully access the synchronization status, return; otherwise, the thread will enter the synchronization queue.
Public final void acquire(int arg) {public final void acquire(int arg) {// If the thread fails to acquire the lock, it will join the synchronization queue; if the thread succeeds in acquiring the lock, it will wake up to attempt to acquire the lock until the thread is interrupted or acquire the lock. tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code

  • Void acquireInterruptibly(int ARg): Similar to acquire above, but the current method throws InterruptedException and returns if the thread is interrupted while acquiring the lock.
Public final void acquireInterruptibly(int arg) throws InterruptedException {if (thread.interrupted ()) throw new InterruptedException(); // If the lock is not acquired if (! TryAcquire (ARG)) // Continuous spin attempts to obtain the lock doAcquireInterruptibly(ARG); }Copy the code

  • Boolean tryAcquireNanos(int arg, long nanosTimeout): Add a timeout to the acquireInterruptibly() method, return true if the lock is obtained within the timeout period, Otherwise return false.
public final boolean tryAcquireNanos(int arg, Long nanosTimeout) throws InterruptedException {// If (thread.interrupted ()) throw new InterruptedException(); / / get corresponding time lock timeout returns false return tryAcquire (arg) | | doAcquireNanos (arg, nanosTimeout); }Copy the code

  • Void acquireShared(int arg): Shared acquireShared(int arg): Shared acquireShared(int arg): Shared acquireShared(int arg): Shared acquireShared(int arg): Shared acquireShared(int arg): Shared acquireShared(int arg)
Public final void acquireShared(int arg) {public final void acquireShared(int arg) { If (tryAcquireShared(arg) < 0) doAcquireShared(arg); }Copy the code

  • Void acquireSharedInterruptibly (int arg) : similar to acquireShared method, only if the thread interrupts, the current method throws an exception.
Public final void acquireSharedInterruptibly (int arg) throws InterruptedException throws an exception if {/ / interrupt (Thread. Interrupted ()) throw new InterruptedException(); / / if failed to get the lock, the spin constantly try to acquiring a lock if (tryAcquireShared (arg) < 0) doAcquireSharedInterruptibly (arg); }Copy the code

  • Boolean tryAcquireSharedNanos (int arg, long nanosTimeout) : on the basis of acquireSharedInterruptibly method increased the timeout.
public final boolean tryAcquireSharedNanos(int arg, Long nanosTimeout) throws InterruptedException {// Interrupt throws an exception if (thread.interrupted ()) throw new InterruptedException(); / / in a timeout time if failed to get the lock, is constantly spin locks return tryAcquireShared (arg) > = 0 | | doAcquireSharedNanos (arg, nanosTimeout); }Copy the code

  • Boolean release(int arg): Exclusively releases the synchronization state. This method wakes up the thread containing the first node in the queue after releasing the synchronization state.
Public final Boolean release(int arg) {// If (tryRelease(arg)) {Node h = head; if (h ! = null && h.waitStatus ! Succeeded = 0) // Wake up the first node's threads unparkprecursor (h); return true; } return false; }Copy the code

  • Boolean releaseShared(int ARG): Shared release synchronization status.
Public Final Boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {// Wake up the thread doReleaseShared(); return true; } return false; }Copy the code

  • Collection getQueuedThreads(): Gets a Collection of threads waiting on a synchronous queue.
public final Collection<Thread> getQueuedThreads() { ArrayList<Thread> list = new ArrayList<Thread>(); for (Node p = tail; p ! = null; p = p.prev) { Thread t = p.thread; if (t ! = null) list.add(t); } return list; }Copy the code

The template methods provided by synchronizers fall into three basic categories:

  • Exclusive get and release synchronization state
  • Shared get and release synchronization state
  • Query a collection of waiting threads in a synchronization queue

5 Customize synchronization components based on the synchronizer

This section describes some of the rewritable methods and template methods provided by AQS. Next, we define an exclusive lock (only one thread can acquire the lock at a time, and other threads can only be in the synchronization queue. When the thread that acquired the lock releases the lock, the next thread can acquire the lock).

Public class ExclusiveLock implements Lock {/** * private static class implements Lock AbstractQueuedSynchronizer {/ / determine whether in the occupied state @ Override protected Boolean isHeldExclusively () {return getState () = = 1; } @override protected Boolean tryAcquire(int arg) {// Set synchronization status through CAS (true on success false on failure) if (compareAndSetState(0, 1)){ setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; @sneakythrows @override protected Boolean tryRelease(int arg) {// If the synchronization status is not locked, an exception is thrown. If (getState() == 0){throw new IllegalAccessException(); } // Release lock setExclusiveOwnerThread(null); setState(0); return true; } / / return a Condition, every Condition includes a Condition queue protected Condition newCondition () {return new ConditionObject (); } } private final Sync sync = new Sync(); @override public void lock() {// Sync.acquire (1); } @override public void lockInterruptibly() throws InterruptedException { Otherwise the spin lock sync. AcquireInterruptibly (1); } @override public Boolean tryLock() {return sync.tryacquire (1); } @override public Boolean tryLock(long time, TimeUnit unit) throws InterruptedException { Or try to spin lock in effective time return sync. TryAcquireSharedNanos (1 unit), toNanos (time)); } @override public void unlock() {sync. Release (1); } @override public Condition newCondition() {// return Condition sync.newcondition (); }}Copy the code

In the code above, we customize an exclusive lock that allows only one thread to own the lock at a time. The sync inner class inherits the synchronizer and implements exclusive access to and release of synchronization state. In the tryAcquire(int arg) method, if the CAS setting succeeds, the synchronization state is obtained, whereas in the tryRelease(int arg) method, the synchronization state is simply reworked to 0. When using the ‘ExclusiveLock’ mode, users do not directly deal with the internal synchronizer. The user can only use the method provided by the ‘ExclusiveLock’ mode. For example, the lock() method is used to add the lock. Releasing the lock will raise an exception if no thread acquires the lock. You can also try to acquire the lock at a specified time, and so on.

At the end

Originally wanted to write some synchronizer implementation principle, the result looked at the length really want to have a little long, then divided into two to write, if read the feeling is helpful, please help to point a thumbup, thank you, good luck next article goodbye!