zoukankan      html  css  js  c++  java
  • 深入浅出ReentrantLock源码解析

    ReentrantLock不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助。

    前提条件

    在理解ReentrantLock时需要具备一些基本的知识

    理解AQS的实现原理

    之前有写过一篇《深入浅出AQS源码解析》关于AQS的文章,对AQS原理不了解的同学可以先看一下

    什么是可重入锁

    当一个线程已经持有锁,如果该现在再次获取锁,是否可以获取成功?如果能获取成功则说明该锁是可重入的,否则是不可重入的

    什么是公平锁和非公平锁

    公平与非公平的一个很本质的区别就是,是否遵守FIFO(也就是先来后到)。当有多个线程来申请锁的时候,是否先申请的线程先获取锁,后申请的线程后获取锁?如果是的,则是公平锁,否则是非公平锁

    更准确地说,先申请锁的线程先获得锁竞争的权利。对于公平的排他锁而言,先申请锁的线程会先获取锁,但是对于公平的共享锁而言,先申请锁的线程会先拥有获取锁竞争的权利,其他等待共享锁的线程也会被唤醒,有可能后唤醒的线程先获取锁。

    ReentrantLock 源码解析

    ReentrantLock的功能主要是通过3个内部类SyncFairSyncNonfairSync来实现的,这3个内部类继承了AbstractQueuedSynchronizer,其中FairSyncNonfairSync类继承了Sync,接下来我们一一解读这几个内部类。

    ReentrantLock.Sync类源码解析

    由于ReentrantLock.Sync类中的核心代码比较少,原理也比较简单,所以就直接在代码中通过详细注释的方式来解读

    abstract static class Sync extends AbstractQueuedSynchronizer {
    
        /**
         * 定义了一个抽象方法,用来获取锁
         */
        abstract void lock();
    
        /**
         * NonfairSync中tryAcquire和、ReentrantLock.tryLock会使用到
         * 重要功能:快速尝试获取锁,如果能够获取锁返回true,否则返回false
         * 在尝试获取锁的过程中,不会阻塞当前线程,一般情况下是当前线程已经持有锁时
         * 才有可能是可以直接获取锁,这也是可重入功能的核心实现
         */
        final boolean nonfairTryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            /**
             * state是AQS对外提供的一个变量,让不同的实现类可以通过这个变量
             * 来控制锁被线程获取锁的次数
             */
            int c = getState();
            // 当state为0表示该锁是没有被任何线程持有
            if (c == 0) {
               /**
                * CAS操作如果成功,说明当前线程竞争到了锁资源,
                * 否则被其他线程竞争到了,当前线程需要进入AQS的同步队列
                * 对于尝试修改state的值的线程可以同时是多个,
                * 他们之间没有先后顺序,这也是非公平的重要体现
                */
                if (compareAndSetState(0, acquires)) {
                  /**
                    * 当前线程已经持有锁了,设置锁的占有者
                    */
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
          /**
            * 如果持有锁的线程是当前线程,可以继续尝试获取锁
            * 这也是可重入的重要体现
            */
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
               /**
                * state是int类型,也就是可重入次数不能低于Integer.MAX_VALUE
                */
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
               /**
                * 获取锁以后直接设置state的值
                */
                setState(nextc);
                return true;
            }
           /**
            * 如果一个线程既不是第一次获取锁,又不是已经获取锁,
            * 则该线程无法获取锁,需要进入AQS的同步队列排队
            */
            return false;
        }
    
        protected final boolean tryRelease(int releases) {
           /**
            * 计算释放releases个资源后state的值
            */
            int c = getState() - releases;
           /**
            * 持有锁的线程如果不是当前线程,无法释放资源
            */
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
           /**
            * 当所有的资源全部释放掉(c=0)时,锁的持有者需要设置为null,
            * 让后续线程可以来竞争锁
            */
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
           /**
            * 修改state的状态
            */
            setState(c);
            return free;
        }
    
        protected final boolean isHeldExclusively() {
            /**
            * 当前线程是否持有锁
            */
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    
    

    ReentrantLock.NonfairSync类源码解析

    static final class NonfairSync extends Sync {
        /**
         * 非公平锁,对外获取锁的步骤:
         * 首先,尝试修改state的状态(从0修改成1),如果修改成功说明当前没有任何线程持有锁
         * 如果线程获取到锁,则把锁的持有线程设置为当前线程
         * 如果无法获取锁,说明锁已经被线程持有,有两种情况:
         * 情况1:持有锁的线程是当前线程,可以走可重入的流程
         * 情况2:持有锁的线程不是当前线程,需要进入AQS去排队
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    
        /**
         * 尝试快速获取锁
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    

    ReentrantLock.FairSync类源码解析

    static final class FairSync extends Sync {
        /**
         * 阻塞方式获取锁
         */
        final void lock() {
            acquire(1);
        }
    
        /**
         * 尝试获取公平锁,与上面分析的nonfairTryAcquire方法很类似,
         * 重点描述彼此之间的区别
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                /**
                 * 公平锁与非公平锁很大的一个区别是:
                 * 在尝试获取锁的时候,如果AQS的同步队列中有其他线程在等待获取锁
                 * 则尝试获取锁失败,需要进入AQS的同步队列排队
                 * hasQueuedPredecessors方法判断AQS的同步队列是否有线程在等待
                 */
                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;
        }
    }
    

    ReentrantLock类源码解析

    ReentrantLock类的实现方式比较简单,主要是依靠NonfairSyncFairSync实现的功能

    public class ReentrantLock implements Lock, java.io.Serializable {
    
        private final Sync sync;
    
        /**
         * 默认是非公平锁
         */
        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
        /**
         * 获取锁,获取的时候申请1个资源
         */
        public void lock() {
            sync.lock();
        }
    
        /**
         * 可中断的方式获取锁
         */
        public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(1);
        }
    
        /**
         * 
         * 尝试获取锁,公平锁和非公平锁都是直接去尝试获取锁
         * 一般在使用该方法的时候,如果尝试获取锁失败,会有后续操作,
         * 可能是直接调用lock以阻塞的方式来获取锁
         */
        public boolean tryLock() {
            return sync.nonfairTryAcquire(1);
        }
    
        /**
         * 带有超时时间的方式尝试获取锁
         */
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }
    
        /**
         * 释放锁,释放掉1个资源
         */
        public void unlock() {
            sync.release(1);
        }
    }
    

    总结

    • 对于已经持有锁的线程,优先申请到资源
    • 对与没有持有锁的线程,需要等待持有锁的线程释放掉所有资源,包括可重入时申请到的资源
    • 公平锁在申请资源的时候要先检查AQS同步队列中是否有等待的线程,也就线程获取锁是按照FIFO的方式
  • 相关阅读:
    php多态
    ssl certificate problem: self signed certificate in certificate chain
    test plugin
    open specific port on ubuntu
    junit vs testng
    jersey rest service
    toast master
    use curl to test java webservice
    update folder access
    elk
  • 原文地址:https://www.cnblogs.com/pinxiong/p/13304210.html
Copyright © 2011-2022 走看看