美文网首页
Java并发之ReentrantLock详解

Java并发之ReentrantLock详解

作者: lisx_ | 来源:发表于2020-06-07 23:47 被阅读0次

    一、ReentrantLock

    ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式,以lock()为例,其使用方式为:

            ReentrantLock takeLock = new ReentrantLock();
            
            // 获取锁
            takeLock.lock();
            
            try {
              
              // 业务逻辑
              
            } finally {
              // 释放锁
              takeLock.unlock();
            }
    

    那么,ReentrantLock内部是如何实现锁的呢?接下来我们就以JDK1.7中的ReentrantLock的lock()为例详细研究下。

    二、ReentrantLock类的结构

    ReentrantLock类实现了Lock和java.io.Serializable接口,其内部有一个实现锁功能的关键成员变量Sync类型的sync,定义如下:

        /** Synchronizer providing all implementation mechanics */
        private final Sync sync;
    

    而这个Sync是继承了AbstractQueuedSynchronizer的内部抽象类,主要由它负责实现锁的功能。关于AbstractQueuedSynchronizer我们会在以后详细介绍,你只要知道它内部存在一个获取锁的等待队列及其互斥锁状态下的int状态位(0当前没有线程持有该锁、n存在某线程重入锁n次)即可,该状态位也可用于其它诸如共享锁、信号量等功能。
    Sync在ReentrantLock中有两种实现类:NonfairSync、FairSync,正好对应了ReentrantLock的非公平锁、公平锁两大类型。

    三、获取锁主体流程

    ReentrantLock的锁功能主要是通过继承了AbstractQueuedSynchronizer的内部类Sync来实现的,其lock()获取锁的主要流程如下:

    • 首先,ReentrantLock的lock()方法会调用其内部成员变量sync的lock()方法;
    • 其次,sync的非公平锁NonfairSync或公平锁FairSync实现了父类AbstractQueuedSynchronizer的lock()方法,其会调用acquire()方法;
    • 然后,acquire()方法则在sync父类AbstractQueuedSynchronizer中实现,它只有一段代码:
        public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    

    通过tryAcquire()方法试图获取锁,获取到直接返回结果,否则通过嵌套调用acquireQueued()、addWaiter()方法将请求获取锁的线程加入等待队列,如果成功的话,将当前请求线程阻塞,and,over!
    队列如何实现及如何添加到队列中以后再做详细分析!这里只关注ReentrantLock的实现逻辑。

    上述就是公平锁、非公平锁实现获取锁的主要流程,而针对每种锁来说,其实现方式有很大差别,主要就体现在各自实现类的lock()和tryAcquire()方法中。在sync的抽象类Sync及其抽象父类AbstractQueuedSynchronizer中,lock()方法和tryAcquire()方法被定义为抽象方法或者未实现,而是由具体子类去实现:

            /**
             * Performs {@link Lock#lock}. The main reason for subclassing
             * is to allow fast path for nonfair version.
             */
            abstract void lock();
        /**
         * Attempts to acquire in exclusive mode. This method should query
         * if the state of the object permits it to be acquired in the
         * exclusive mode, and if so to acquire it.
         *
         * <p>This method is always invoked by the thread performing
         * acquire.  If this method reports failure, the acquire method
         * may queue the thread, if it is not already queued, until it is
         * signalled by a release from some other thread. This can be used
         * to implement method {@link Lock#tryLock()}.
         *
         * <p>The default
         * implementation throws {@link UnsupportedOperationException}.
         *
         * @param arg the acquire argument. This value is always the one
         *        passed to an acquire method, or is the value saved on entry
         *        to a condition wait.  The value is otherwise uninterpreted
         *        and can represent anything you like.
         * @return {@code true} if successful. Upon success, this object has
         *         been acquired.
         * @throws IllegalMonitorStateException if acquiring would place this
         *         synchronizer in an illegal state. This exception must be
         *         thrown in a consistent fashion for synchronization to work
         *         correctly.
         * @throws UnsupportedOperationException if exclusive mode is not supported
         */
        protected boolean tryAcquire(int arg) {
            throw new UnsupportedOperationException();
        }
    

    下面,我们分别研究下非公平锁和公平锁的实现。

    四、非公平锁NonfairSync

    1、lock()方法

            /**
             * 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);
            }
    

    通过代码可以看到,非公平锁上来就无视等待队列的存在而抢占锁,通过基于CAS操作的compareAndSetState(0, 1)方法,试图修改当前锁的状态,这个0表示AbstractQueuedSynchronizer内部的一种状态,针对互斥锁则是尚未有线程持有该锁,而>=1则表示存在线程持有该锁,并重入对应次数,这个上来就CAS的操作也是非公共锁的一种体现,CAS操作成功的话,则将当前线程设置为该锁的唯一拥有者。
    抢占不成功的话,则调用父类的acquire()方法,按照上面讲的,继而会调用tryAcquire()方法,这个方法也是由最终实现类NonfairSync实现的,如下:

            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
    

    2、tryAcquire()

    而这个nonfairTryAcquire()方法实现如下:

            /**
             * Performs non-fair tryLock.  tryAcquire is
             * implemented in subclasses, but both need nonfair
             * try for trylock method.
             */
            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;
            }
    

    还是上来先判断锁的状态,通过CAS来抢占,抢占成功,直接返回true,如果锁的持有者线程为当前线程的话,则通过累加状态标识重入次数。抢占不成功,或者锁的本身持有者不是当前线程,则返回false,继而后续通过进入等待队列的方式排队获取锁。

    五、公平锁FairSync

    1、lock()

        公平锁的lock()方法就比较简单了,直接调用acquire()方法,如下:
    
            final void lock() {
                acquire(1);
            }
    

    2、tryAcquire()

        公平锁的tryAcquire()方法也相对较简单,如下:
    
            /**
             * 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;
            }
    

    当前线程会在得到当前锁状态为0,即没有线程持有该锁,并且通过!hasQueuedPredecessors()判断当前等待队列没有前继线程(也就是说,没有比我优先级更高的线程在请求锁了)获取锁的情况下,通过CAS抢占锁,并设置自己为锁的当前拥有者,当然,如果是重入的话,和非公平锁处理一样,通过累加状态位标记重入次数。
    而一旦等待队列中有等待者,或当前线程抢占锁失败,则它会乖乖的进入等待队列排队等待。

    六、默认实现

    ReentrantLock的默认实现为非公平锁,如下:

       /**
        * Creates an instance of {@code ReentrantLock}.
        * This is equivalent to using {@code ReentrantLock(false)}.
        */
       public ReentrantLock() {
           sync = new NonfairSync();
       }
    

    当然,你也可以通过另外一个构造方法指定锁的实现方式,如下:

        /**
         * Creates an instance of {@code ReentrantLock} with the
         * given fairness policy.
         *
         * @param fair {@code true} if this lock should use a fair ordering policy
         */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    

    七、其它

        即便是公平锁,如果通过不带超时时间限制的tryLock()的方式获取锁的话,它也是不公平的,因为其内部调用的是sync.nonfairTryAcquire()方法,无论抢到与否,都会同步返回。如下:
    
        public boolean tryLock() {
            return sync.nonfairTryAcquire(1);
        }
    

    但是带有超时时间限制的tryLock(long timeout, TimeUnit unit)方法则不一样,还是会遵循公平或非公平的原则的,如下:

        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }
    

    相关文章

      网友评论

          本文标题:Java并发之ReentrantLock详解

          本文链接:https://www.haomeiwen.com/subject/gxykqctx.html