Source ReentrantLock inquiry

ReentrantLockIs a reentrant lock, reentrant is to say the same thread can acquire the same lock several times, there will be a corresponding internal field records the number of re-entry, it is also a mutex, meaning that only one thread can be obtained reentrant lock.

1. Constructor

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLockProviding two constructors, constructor simply used to initialize the syncfield, you can see, by default ReentrantLocknon-equitable use of the lock, of course, also possible to use Boolean parameter with a constructor to choose Equity lock. Lock lock fair and unfair implementation relies on two inner classes: FairSyncand NonfairSync, then learn what these two classes:

    //非公平锁
    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);
        }
    }

    static final class 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 (!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;
        }
    }

The two inner class code is very short, and inherit another inner class Sync. Here in no hurry to introduce Syncclass because the class itself is not complicated, incidentally, to explain the need for subsequent method which is used at present only need to know this class inherits AbstractQueuedSynchronizer(AQS)can be.

2. The common method

  • lock()
    public void lock() {
        sync.lock();
    }

lockThe method provides a locking feature, fair and unfair lock lock lock operation is not the same, take a look at the details of unfair lock, followed explain fair locks.

  • Unfair lock lock logic
    final void lock() {
        //使用CAS操作,尝试将state字段从0修改为1,如果成功修改该字段,则表示获取了互斥锁
        //如果获取互斥锁失败,转入acquier()方法逻辑
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    
    //设置获得了互斥锁的线程
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

For non-locking fair terms, using lock()method one up on the attempt to acquire the mutex for success will exclusiveOwnerThreadpoint to themselves, that means it will hold its own lock, otherwise execute acquire()logic method, the following of acquire()logical methods analyzed one by one.
The first is the tryAcquire()method, unfair lock overrides this method is called internally and Syncclass nonfairTryAcquire().

    //从上面的逻辑来看,这里的acquires=1
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //c==0,说明当前处于未加锁状态,锁没有被其他线程获取
        if (c == 0) {
            //在锁没有被其他线程占有的情况下,非公平锁再次尝试获取锁,获取成功则将exclusiveOwnerThread指向自己
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //执行到这里说明锁已经被占有,如果是被自己占有,将state字段加1,记录重入次数
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            //当nextc达到int类型最大值时会溢出,因此可重入次数的最大值就是int类型的最大值
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //执行到这里说明:1)锁未被占有的情况下,抢锁失败,说明当前有其他线程抢到了锁;2)锁已经被其他线程占有
        //即只要当前线程没有获取到锁,就返回false
        return false;
    }
    
    //获取state字段,该字段定义在AQS中
    protected final int getState() {
        return state;
    }
    //设置state字段
    protected final void setState(int newState) {
        state = newState;
    }

Not the current thread tryAcquire()acquiring the lock method, will first perform addWaiter(Node.EXCLUSIVE)a method, wherein the parameter Node.EXCLUSIVEis a constant, which is defined static final Node EXCLUSIVE = null, it is to tag attributes are mutex lock. addWaiter()The role of the current method is to thread packaged into a Nodenode, into the tail of the queue, the method introduced CountDownLatchin detail explained before class, friends who are interested can refer to ConcurrentHashMap explore the source (the JDK 1.8) , this will not repeat.
After the addition of the current thread to wait on the queue, then execution acquireQueued()method, the following source code:

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                //获取当前节点的前一个节点
                final Node p = node.predecessor();
                //如果前一个节点是头节点,说明当前节点排在队首,非公平锁会则再次通过tryAcquire方法获取锁
                if (p == head && tryAcquire(arg)) {
                    //将自己设置为头节点
                    setHead(node);
                    //前一个头结点没用了,会被垃圾回收掉
                    p.next = null; // help GC
                    failed = false;
                    //正常结束,返回false,注意该字段可能会在下面的条件语句中被改变
                    return interrupted;
                }
                //如果前一个节点不是头节点,或者当前线程获取锁失败,会执行到这里
                //shouldParkAfterFailedAcquire()方法只有在p的状态是SIGNAL时才返回false,此时parkAndCheckInterrupt()方法才有机会执行
                //注意外层的自旋,for循环体会一直重试,因此只要执行到这里,总会有机会将p设置成SIGNAL状态从而将当前线程挂起
                //另外,如果parkAndCheckInterrupt()返回true,说明当前线程设置了中断状态,会将interrupted设置为true,代码接着自旋,会在上一个条件语句中返回true
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //如果在自旋中线程被中断或者发送异常,failed字段的值将会为true,这里会处理这种情况,放弃让当前线程获取锁,并抛出中断异常
            if (failed)
                cancelAcquire(node);
        }
    }
    //方法逻辑是:只有在前置节点的状态是SIGNAL时才返回true,其他情况都返回false
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        //删除当前节点之前连续状态是CANCELLED的节点
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    //线程在这里阻塞,并在被唤醒后检查中断状态
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    
    //
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;

        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                //唤醒后一个节点
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

Note that acquireQueued()either interrupt exception will be thrown, or return to normal end false, only to be awakened after setting the thread interrupt status will be returned true. Comparison can be found, acquireQueued()the logic and methods CountDownLatchof doAcquireSharedInterruptibly()very similar, in many ways CountDownLatchspoke through this blog, this article will not go into details on these methods.
Introduced over acquire()method, go back to the logical method:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

If tryAcquire()there is no method to acquire the lock, then the current thread is added to the waiting queue the tail, to see whether the previous node node is the first node is then the current thread can continue down, otherwise it will clog hang. When acquireQueuedreturning true, explain thread sets up interrupt status, it calls selfInterrupt()interrupt the thread, otherwise selfInterrupt()the method no chance to execute.
Here unfair to lock the locking process has been introduced over, due to the relatively long code logic, process Look at source will switch back and forth several classes, the idea is very easy to break, reading the code time to pay attention. (It is necessary to make up a flow chart).

  • Fair lock lock logic
    let's look at the logic lock fair locks:
    final void lock() {
        acquire(1);
    }

Compared with non-fair lock, the lock is not fair to grab a lock up logic, which is the manifestation of fairness. Other kinds of locks in acquire()the same manner as the framework, but with different implementation details, take a look at fair locks tryAcquire()methods:

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //c=0表示当前没有其他线程持有锁
        if (c == 0) {
            //下面的代码与非公平锁相比,多了hasQueuedPredecessors()方法的处理逻辑,公平锁只有在前面没有其他线程排队的情况下才会尝试获取锁
            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;
        }
        //只要当前线程没有获取到锁,就返回false
        return false;
    }

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        //h != t表示等待队列中有其他节点
        //h.next == null可能会有点费解,按理说h!=t之后,h后面肯定会有节点才对,这种情况其实已经见过,在上文介绍acquireQueued()方法时说过,
        //被唤醒的第一个等待节点会将自己设置为头结点,如果这个节点是队列中的唯一节点的话,它的下一个节点就是null
        //至于s.thread != Thread.currentThread()这个条件暂时可以忽略,因为公平锁执行到hasQueuedPredecessors方法时根本还没有入队,
        //这也意味着,只要队列中有其他节点在等候,公平锁就要求其他线程排队等待
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
  • lockInterruptibly
    As the name suggests, lockInterruptiblyyou can respond to interrupt, to look at the implementation of the method:
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //先尝试获取锁,获取失败才执行后面的逻辑
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

lockInterruptibly()The method is almost acquire()exactly the same method, the only difference is the acquire()method, parkAndCheckInterruptbecause the thread interrupted status set to return truewhen, simply set a bit interruptedvalue of the field, and lockInterruptibly()is a direct throw.

  • unlockThe method of
    introduction to the locked logic, let's look at the logic unlock:
    public void unlock() {
        sync.release(1);
    }

    public final boolean release(int arg) {
        //如果成功释放了锁,则执行下面的代码块
        if (tryRelease(arg)) {
            Node h = head;
            //如果头节点不为null,请求节点状态不是初始状态,就释放头结点后第一个有效节点
            //问题:这里为什么需要判断头结点的状态呢???
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    //
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        //线程没有持有锁的情况下,不允许释放锁,否则会抛异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //可重入性的判断,如果释放了一次锁,使得c=0,就指针释放锁,做法是将记录锁的字段exclusiveOwnerThread重新指向null
        //注意,只有最后一次释放可重入锁,才会返回true
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    //唤醒node节点的下一个有效节点,这里的有效指的是状态不是CANCELLED状态的节点
    private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • newCondition()
    ReentrantLockYou can bind multiple wait condition, this function is newCondition()implemented methods, each call newCondition()when the method will generate a new ConditionObjectobject, which is AQSan internal class, the code is very long, not discussed in detail here. Simply look at the source of the method:
    public Condition newCondition() {
        return sync.newCondition();
    }
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

3. Summary

In a multithreaded environment, ReentrantLockthe lock unfair than fair locks have higher performance because the lock to avoid unfair thread hangs overhead context switch produced, but fair thread lock to avoid hunger, and therefore each have their own scenes to be used. From the source point of view, J.U.Cthe many types of packages are dependent AQSclass, so it is necessary to get to know AQS. He mentioned ReentrantLock, always found and synchronizedcompared. synchronizedAlso reentrant, and in JDK 1.6the future, synchronizedthe performance has been greatly improved, and therefore choose to use ReentrantLockgenerally consider the use of its three advantages: interruptible, can achieve a fair lock, can be bound to a number of conditions, these advantages It is synchronizednot available.

Guess you like

Origin www.cnblogs.com/NaLanZiYi-LinEr/p/12508195.html