JAVA multithreading part three (two) AQS

Concurrent Notes Portal:
1.0 Concurrent Programming-Mind Map
2.0 Concurrent Programming-Thread Safety Fundamentals
3.0 Concurrent Programming-Basic Building Module
4.0 Concurrent Programming-Task Execution-Future
5.0 Concurrent Programming-Multi-threaded Performance and Scalability
6.0 Concurrent Programming- Explicit lock and synchronized
7.0 concurrent programming-AbstractQueuedSynchronizer
8.0 concurrent programming-atomic variables and non-blocking synchronization mechanism

In ReentrantLockand Semaphorethere is much in common between the two interfaces. Both of these classes can be used as a "valve" that allows only a certain number of threads through, and when the thread reaches the valve, by (the call lockor acquiresuccessfully returns), you can also wait for (the call lockor acquirewhen Blocking), or cancel ( return "false" when calling tryLockor tryAcquire, which means that the lock is unavailable within the specified time or cannot get permission). Moreover, these two interfaces both support interruptible, non-interruptible, and time-limited acquisition operations, and both support waiting threads to perform fair or unfair queue operations.

In fact, they all use a common base class, namely AbstractQueuedSynchronizer( AQS), which is also the base class of many other synchronous classes. AQSIt is a framework for building locks and synchronizers. Many synchronizers can be constructed easily and efficiently through AQS. Not only ReentrantLockand Semaphoreis based on the AQSbuilding, but also include CountDownLatch, ReentrantReadWriteLock, SynchronousQueueand FutureTask.

Acquisition and release in AQS

In the synchronizer class based on AQS, the most basic operations include various forms of lock acquisition and release operations. And the get operation is a state-dependent operation and usually blocks.
The following pseudo code shows the simple logic of AQS acquisition and release. (Douge Lea's source code is too delicate, so you have to taste it slowly)

    boolean acquire() throws InterruptedException{
    
    
        while (当前状态不允许获取操作){
    
    
            if (需要阻塞获取请求){
    
    
                如果当前线程不再队列中,则将其插入队列
                阻塞当前线程
            }else {
    
    
                返回失败
            }
        }
        更新同步器的状态
        如果线程位于队列中,则将其移除队列
        返回成功
    }
    void release(){
    
    
        更新同步器的状态
        if(新的状态允许某个阻塞的线程获取成功){
    
    
            解除队列中一个或多个线程的阻塞状态
        }
    }

A get operation consists of two parts:

  • First, the synchronizer judges whether the current state allows the acquisition operation, and if so, the thread is allowed to execute, otherwise the acquisition operation will block or fail. This judgment is based on the semantics of the synchronizer. For example: for a lock, if it is not held by a thread, it can be successfully acquired; for a lock, if it is in the end state, it can also be successfully acquired.
  • Secondly, it is to update the state of the synchronizer. A thread that obtains the synchronizer may affect whether other threads can obtain the synchronizer. For example, when acquiring a lock, lock state will "not prepared to hold" to "has been held", and from Semaphorepost-acquisition license, will reduce the number of licenses 1. However, when a thread acquires a lock, it does not affect whether other threads can acquire it.

Depending on the nature of the synchronizer, the implementation methods are different:

  • Exclusive operation (for example: ReentrantLock): If a synchronizer get exclusive support operations, the need to achieve AQSthe tryAcquire, tryRelease, tryHeldExeclusivelyand other methods.
  • Non-exclusive operation (for example: Semphore, CountDownLatch): support for synchronization acquisition is shared, it should be realized tryAcquireShared, tryReleaseSharedother methods

AQSIn the acquire, acquireShared, release, releaseSharedand other methods will call these methods subclasses prefixed tryversion to determine whether an operation can be performed.

A simple lock

OneShotLatchContains two public methods: awaitand signal, which correspond to the acquisition and release operations respectively. At first, the latch is closed, any calling awaitthread will be blocked until the blocking and open. When by calling signalthe open latch, all the threads of milk waiting to be released, and then reaches the locking thread are also allowed to be executed.

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class OneShotLatch {
    
    
    private final Sync sync = new Sync();

    public void signal() {
    
    
        sync.releaseShared(0);
    }

    public void await() throws InterruptedException {
    
    
        sync.acquireSharedInterruptibly(0);
    }

    private static class Sync extends AbstractQueuedSynchronizer {
    
    
        @Override
        protected int tryAcquireShared(int arg) {
    
    
            int state = getState();
            //如果闭锁是开的(state==1),那么这个操作讲成功,否则失败
            System.out.println("state = " + state);
            return getState() == 1 ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
    
    
            setState(1);//打开闭锁
            return true;//其他线程可以获取该闭锁
        }
    }

    public static void main(String[] args) {
    
    
        OneShotLatch osl = new OneShotLatch();
        new Thread(() -> {
    
    
            System.out.println("we are in main 01 thread, and start osl await");
            try {
    
    
                osl.await();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("main 01 thread osl await finished");
        }).start();
        new Thread(() -> {
    
    
            System.out.println("we are in main 02 thread, and start osl await");
            try {
    
    
                osl.await();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("main 02 thread osl await finished");
        }).start();
        new Thread(() -> {
    
    

            System.out.println("we are in main 03 thread, and first sleep 5s");
            try {
    
    
                Thread.sleep(5000);
                System.out.println("we are in main 03 thread, and start osl await");
                osl.await();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("main 03 thread osl await finished");
        }).start();
        new Thread(() -> {
    
    
            System.out.println("we are in work thread,and we start waiting");
            try {
    
    
                Thread.sleep(3000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("work finish,now we signal main thread");
            osl.signal();
        }).start();
    }
}

AQS in java.util.concurrent

And contracting can be blocked in a number of categories, e.g. ReentrantLock, Semaphore, CountDownLatch, ReentrantReadWriteLock, SynchronousQueueand FutureTaskthe like, are based AQSconstruction.

ReentrantLock

ReentrantLockOnly supports exclusive access to operate, so it implements tryAcquire, tryReleaseand isHeldExclusivelymethods.

  • ReentrantLockThe synchronization state is stateused to save the number of lock acquisition operations.
  • It maintains a ownervariable to hold the current thread, but was reconstructed increased on 1.6 AbstractOwnableSynchronizerwith exclusiveOwnerThreadto save the current thread. This variable is only modified when the current thread has just acquired the lock or is about to release the lock.
    • In tryReleasethe inspection ownerfield, so as to ensure the implementation of the current thread unlockbefore the operation has already acquired a lock
    • In tryAcquireuse in the ownerfield determines whether the acquired operation is re-entrant or competition

Unfair lock version (default)

    static final class 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) {
    
    
            return nonfairTryAcquire(acquires);
        }
    }
        final boolean nonfairTryAcquire(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;
        }

When a thread tries to acquire a lock, it tryAcquirewill first check the state of the lock:

  • Not hold: The compareAndSetState(0, acquires)update lock attempt to represent atomic operation has been held.
  • Already holds: determine whether the current site is the lock owner, is: count is incremented (it ReentrantLockis reentrant lock); not: Get the operation failed.

Semaphore versus CountDownLatch

SemaphoreAnd CountDownLatchis part of a shared support obtained synchronizer, they realized tryAcquireSharedand tryReleaseSharedmethods

SemaphoreThe stateavailable license number is currently used for storage.

Take Semaphorethe implementation of unfair locks as an example:

    static final class NonfairSync extends Sync {
    
    
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
    
    
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
    
    
            return nonfairTryAcquireShared(acquires);
        }
    }
        final int nonfairTryAcquireShared(int acquires) {
    
    
            for (;;) {
    
    
                int available = getState();
                int remaining = available - acquires;
                //这个代码很精髓啊
                //remaining < 0 :如果没有足够的许可,退出循环
                //compareAndSetState设置成功,退出循环;设置失败,重新尝试
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
        protected final boolean tryReleaseShared(int releases) {
    
    
            for (;;) {
    
    
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

tryAcquireSharedFirst, the number of remaining licenses is calculated.

  • If the number is insufficient, a value will be returned to indicate that the get operation failed.
  • If there are remaining licenses, the license count will compareAndSetStatebe reduced atomically. If the operation is successful (meaning that it has not been modified since the last read), then a value is returned to indicate that the operation was successful.

CountDownLatchUse AQSway with Semaphorevery similar: synchronization status stateto save the current count value of the
await() call relationship:

graph LR
await-->acquireSharedInterruptibly 
acquireSharedInterruptibly --> tryReleaseShared

awaitCall acquire, when the counter is 0, it acquirewill return immediately, otherwise the execution will doAcquireSharedInterruptiblyenter the block.

    private static final class Sync extends AbstractQueuedSynchronizer {
    
    
        Sync(int count) {
    
    
            setState(count);
        }
        int getCount() {
    
    
            return getState();
        }
        protected int tryAcquireShared(int acquires) {
    
    
            return (getState() == 0) ? 1 : -1;
        }
        protected boolean tryReleaseShared(int releases) {
    
    
            // Decrement count; signal when transition to zero
            for (;;) {
    
    
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    
    
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
    public void await() throws InterruptedException {
    
    
        sync.acquireSharedInterruptibly(1);
    }

countDown()Calling relationship:

graph LR
countDown-->releaseShared
releaseShared-->tryReleaseShared

countDown()Call tryReleaseSharedto complete the count is decremented when the count value is 0, execution doReleaseSharedunblock all waiting threads.

    public void countDown() {
    
    
        sync.releaseShared(1);
    }
    public final boolean releaseShared(int arg) {
    
    
        if (tryReleaseShared(arg)) {
    
    
            doReleaseShared();
            return true;
        }
        return false;
    }

FutureTask

The new version of the old man is not used directly AQS, sun.misc.Unsafe UNSAFEand is realized through operation.

ReentrantReadWriteLock

ReadWriteLockIt indicates the presence of two interfaces locks: locks read and write locks, but based AQSimplementation ReentrantReadWriteLock, a single AQSsubclass manage read lock and a write lock.

ReentrantReadWriteLockTwo 16-bit states are used to represent the count of write lock and read lock respectively. Operations on read locks use shared acquisition and release methods; operations on write locks use exclusive acquisition and release methods.
AQSA queue of waiting threads is maintained internally, which records whether a thread has exclusive ( Node.EXCLUSIVE) or shared ( Node.SHARE) access.

summary

AQSSource really complicated, just under Benpian superficial and record contract in AQSusage, the next fight for the next bite AQSrealization of the principle of

Doug LeaThe old man raised the banner of java concurrency alone, really.

The following is a good AQS source code analysis on the Internet, record it

Guess you like

Origin blog.csdn.net/lijie2664989/article/details/105739309