Abstractqueuedsynchronizer (AQS), the base of synchronization class

Abstractqueuedsynchronizer (AQS), the base of synchronization class

We introduced many synchronization classes before, such as reentrantlock, semaphore, countdownlatch, reentrantreadwritelock, futuretask, etc.

AQS encapsulates a large number of details of the design when implementing the synchronizer. It provides wait queues of FIFO and an int state to represent the current state.

According to the JDK, we are not recommended to use AQS directly. We usually need to build an internal class to inherit AQS and override the following methods as needed:

In these methods, we need to call getstate, setstate or compareandsetstate to change the state value.

The above method refers to two operations: exclusive operation (such as reentrantlock) and shared operation (such as semaphore, countdownlatch).

The difference between the two is whether multiple threads can obtain synchronization status at the same time.

For example, if we run multiple threads to read at the same time, but the notification allows only one thread to write, the read lock here is a shared operation, and the write lock is an exclusive operation.

In the synchronization class based on QAS, the most basic operations are get operation and release operation. This state represents the values on which these get and release operations depend.

State is an int value. You can use it to represent any state. For example, reentrantlock uses it to represent the number of times the owner thread repeatedly acquires the lock. Semaphore uses it to represent the remaining permissions, while futuretask uses it to represent the status of the task (start, run, complete or cancel). Of course, you can also customize additional status variables to represent other information.

The pseudocode below represents the form of acquisition and release operations in AQS:

   Acquire:
       while (!tryAcquire(arg)) {
          enqueue thread if it is not already queued;
          possibly block current thread;
       }
  
   Release:
       if (tryRelease(arg))
          unblock the first queued thread;

To obtain the operation, first judge whether the current state allows the acquisition operation. If not, enter the current thread into the queue and may block the current thread.

To release the operation, first judge whether to run the release operation. If allowed, release the thread in the queue and run.

Let's look at a specific implementation:

public class AQSUsage {

    private final Sync sync= new Sync();

    private class Sync extends AbstractQueuedSynchronizer{
        protected int tryAcquireShared(int ignored){
            return (getState() ==1 )? 1: -1;
        }
        protected boolean tryReleaseShared(int ignored){
            setState(1);
            return true;
        }
    }

    public void release() {
        sync.releaseShared(0);
    }
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(0);
    }
}

In the above example, we define an internal class sync. In this class, we implement two methods, tryacquireshared and tryrereleaseshared. In these two methods, we judge and set the value of state.

sync. Releaseshared and sync Acquiressaredinterstructibly calls the tryacquiressared and tryrereleaseshared methods, respectively.

We also mentioned earlier that many synchronization classes are implemented using AQS. We can look at the implementation of tryacquire in other standard synchronization classes.

First, look at reentrantlock:

   final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0,acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

Reentrantlock only supports exclusive locks. So it needs to implement the tryacquire method. In addition, it also maintains an owner variable to save the flag of the current owner thread, so as to realize the reentrant lock.

Let's look at the implementation of semaphore and countdownlatch. Because they are sharing operations, they need to implement the tryacqureshared method:

        final int tryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available,remaining))
                    return remaining;
            }
        }

For examples of this article, please refer to https://github.com/ddean2009/learn-java-concurrency/tree/master/AQS

For more information, please visit flybean's blog

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>