Analysis of Java AQS principle

 AQS stands AbstractQueuedSynchronizer, it is implemented JCU package almost all about locks, the cornerstone of important components of multi-threaded and thread synchronization, etc., the core idea is volatile int statethis property with Unsafethe tools to achieve the current state of the lock to be modified.

1, AQS principles outlined

 AQS interior of CLH maintains a FIFO queue, the queue of a basic structure as follows.

Here Insert Picture Description
In the queue, each node represents a thread, but the thread is not only the head node will be thread-safe process.

1.1, Node node

 AQS used to represent each node of Node CLH queue, the source follows:

    static final class Node {
        //表示共享模式(共享锁)
        static final Node SHARED = new Node();
        //表示独占模式(独占锁)
        static final Node EXCLUSIVE = null;

        //表示线程已取消
        static final int CANCELLED =  1;
        //表示当前结点的后继节点需要被唤醒
        static final int SIGNAL    = -1;
        //线程(处在Condition休眠状态)在等待Condition唤醒
        static final int CONDITION = -2;
        //表示锁的下一次获取可以无条件传播,在共享模式头结点有可能处于这种状态
        static final int PROPAGATE = -3;

        //线程等待状态
        volatile int waitStatus;

        //当前节点的前一个节点
        volatile Node prev;

        //当前节点的下一个节点
        volatile Node next;

        //当前节点所代表的的线程
        volatile Thread thread;

        //可以理解为当前是独占模式还是共享模式
        Node nextWaiter;

        //如果节点在共享模式下等待,则返回true。
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        //获取前一个节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        ...
    }
复制代码

1.2, into the team

 If the current thread acquired the lock fails by CAS, AQS the thread will wait state and other information packaged into a node Node, and added to the tail of the queue synchronization, while the current thread is suspended.

    public final void acquire(int arg) {
        //tryAcquire是子类重写的方法
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //将线程及状态信息打包成一个节点
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            //快速的将节点插入队列尾部,
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //快速插入失败,通过轮询来插入尾部,性能比快速插入消耗要大一些
        enq(node);
        return node;
    }
    //通过轮询方式将节点插入队列尾部
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    //挂起当前线程
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //通过LockSupport来挂起当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
复制代码

 The overall flow diagram of the.

Here Insert Picture Description

And adding a node fails to acquire the lock to the operation of the synchronous queue tail

1.3 out team

 When the lock is released, perform the operations team and wake successor node.

    public final boolean release(int arg) {
        //tryRelease是子类实现的方式
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //线程唤醒及出队操作
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
复制代码

 The overall flow diagram of the.

Here Insert Picture Description

Remove node operation when the lock is released

1.4 synchronous state management

 Perhaps some doubts about the figure of the synchronizer. What in the end is it? Actually very simple, it is to give first and last nodes plus volatilesynchronous domain, as follows.

    private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state;
复制代码

 AQS above three variables is a very important three variables, the first two variables well understood, the following terms about the statevariable, which is a counter in the exclusive lock case, after acquiring the lock, as will the value of state 1, the lock release is set to 0 (which locks belonging to non-reentrant lock); in the case of a shared lock, each thread to acquire the lock, it will state ++, when the lock is released state--; lock in reentrant case (acquired locks are the same lock), will each acquire a lock state ++, when the lock is released state--.

    protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }
    //使用CAS
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
复制代码

2, custom exclusive locks and shared locks

 By the one to explain, we must have a certain understanding of AQS, following on to achieve an exclusive lock and a shared lock by AQS.

 AQS is very strong, you only need to rewrite tryAcquire, tryReleasethese two methods can achieve an exclusive lock. code show as below:

public class SingleLock implements Lock {
    //自定义的独占锁
    static class Sync extends AbstractQueuedSynchronizer {
        //独占锁
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //独占锁
        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        //判断是是否是独占锁。
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

    private Sync sync;

    public SingleLock() {
        sync = new Sync();
    }
    //加锁
    @Override
    public void lock() {
        sync.acquire(1);
    }
    //获取可中断锁
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    //获取锁,可能失败
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    //在time时间内不能获取锁则失败
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }
    //释放锁
    @Override
    public void unlock() {
        sync.release(1);
    }
    //Condition来实现阻塞唤醒机制
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}
复制代码

 Very simple code on the realization of an exclusive lock, SingleLockhas ReentrantLockmost of the functions and usage of the same. Is not it simple...

 JUC Pack for latching (a CountDownLatch) and semaphore (Semaphore) is to achieve the typical shared lock. Shared lock is also very simple, needs to be rewritten tryAcquireShared, tryReleaseSharedthese two methods. Here's to achieve a shared lock. code show as below:

public class ShareLock implements Lock {
    static class Sync extends AbstractQueuedSynchronizer {
        private int count;

        Sync(int count) {
            this.count = count;
        }

        @Override
        protected int tryAcquireShared(int arg) {
            for (; ; ) {
                int current = getState();
                int newCount = current - arg;
                if (newCount < 0 || compareAndSetState(current, newCount)) {
                    return newCount;
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            for (; ; ) {
                int current = getState();
                int newCount = current + arg;
                if (compareAndSetState(current, newCount)) {
                    return true;
                }
            }
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

    private int count;
    private Sync sync;

    public ShareLock(int count) {
        this.count = count;
        sync = new Sync(count);
    }

    @Override
    public void lock() {
        sync.acquireShared(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquireShared(1) >= 0;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.releaseShared(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}
复制代码

ShareLockAllow countthreads simultaneously acquire the lock, it's also very simple to achieve it. With these two examples above, we can follow their own needs to achieve different locks, but JUC package provides classes can basically meet most needs.

3, the lock reentrancy

SingleLockIs an exclusive lock ourselves to achieve, but if you use it in a recursive, it will deadlock. Because SingleLocknot have reentrancy. So how to achieve reentrancy Nigeria? Look ReentrantLockto achieve.

    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()) {
            //状态加1
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                 throw new Error("Maximum lock count exceeded");
            //重新设置状态
            setState(nextc);
            return true;
        }
         return false;
    }

复制代码

 Can be found reentrancy implementation is quite simple, first determines whether the current thread is not already get the lock, the lock will have been given if the state value is incremented. Reentrancy This is very important, otherwise it will cause unnecessary deadlock, Synchronizealso have reentrancy.

4, fairness lock

SingleLockBelonging to a non-arm lock, then how to achieve fair locks Nepal? In fact, this is more simple, just add a judgment can be. Look ReentrantLockto achieve a fair lock.

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //如果当前线程之前还有节点则hasQueuedPredecessors返回true,就不会去竞争锁
                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;
        }
复制代码

hasQueuedPredecessorsThe key is to determine the fairness of the lock, it returns true if there are queued until a thread in the current thread, the current thread at this time would not have to compete lock. Thus ensuring the fairness of the lock.

5, thundering herd effect

 In use wait/notify/notifyAll, the thread is awakened use notifyAllto wake up the thread, because notifynot wake specified thread, which may lead to a deadlock. But using notifyAllalso has a problem that when a large number of threads to acquire the lock will produce shock group effect , a large number of sharp increase in competition will inevitably lead to waste of resources and, therefore, after all, can only have a successful competition thread, other threads or to the old honest real wait to go back. The thundering herd effect problem of AQS FIFO queue waiting to resolve competition concerns in the lock offers a solution: Keep a FIFO queue, the queue of each node only interested in their previous node status, and only wake up thread wakes team head waiting thread .

[References]

AQS-depth study java synchronizer

On Java concurrent programming series (nine) - AQS structure and principle analysis

Java Concurrency of AQS

Pa Pa ReentrantLock as well as a realization of the principle of AQS

Guess you like

Origin juejin.im/post/5d11ea686fb9a07ecc4493b1