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的方式
  • 相关阅读:
    ZINTERSTORE — Redis 命令参考
    Wombat vim colorscheme – customized Chronosbox
    我的vim colorscheme 白色之夜 博客园
    Python list of class attributes Python
    vim配色方案colorscheme设置
    colorscheme install
    Python类中的私有成员(私有函数,私有变量)
    Pebble Overview
    用python 发送邮件
    Python发送带附件的Email
  • 原文地址:https://www.cnblogs.com/pinxiong/p/13304210.html
Copyright © 2011-2022 走看看