目录

  • ReentrantLock简介
  • 基础知识铺垫
    • state属性
    • 线程持有者属性
    • ReentrantLock中的队列使用
  • Demo&原理解析
    • 公平锁-lock()方法
    • Demo
    • 详尽原理
    • 白话原理(面试口述)
    • unLock()方法
    • 详尽原理
    • 白话原理(面试口述)
    • 公平锁-lock()方法
    • Demo
    • 详尽原理
    • 白话原理(面试口述)
    • lockInterruptibly()方法
    • Demo
    • 详尽原理
    • 白话原理(面试口述)
    • tryLock()方法
    • Demo
    • 详尽原理
    • 白话原理(面试口述)
    • tryLock(long timeout, TimeUnit unit)方法
    • Demo
    • 详尽原理
    • 白话原理(面试口述)
  • 面试题汇总
  • ReentrantLock全中文注释可运行源码download

ReentrantLock简介

​ jdk并发包中的可重入锁,是基于AQS(AbstractQueuedSynchronized)实现的,它有公平锁(线程上锁的顺序完全基于调用方法的先后顺序)和不公平锁(线程上锁的顺序不完全基于调用方法的先后顺序)两种实现方式。

​ ReentrantLock提供多种API:公平锁/非公平锁-lock()方法、定时释放锁-tryLock(long timeout, TimeUnit unit)方法、interrupt中断阻塞线程抛出InterruptedException异常、获取锁失败直接返回的tryLock()

​ 本文基于open-jdk 1.8讲解

基础知识铺垫

state属性

    //同步状态标识    
    private volatile int state;

state为0时,代表当前没有线程持有锁;为1时,代表有线程持有锁;如果大于1,因为ReentrantLock为重入锁,所以代表锁被当前线程重入的次数。

使用volatile,保证了state属性值的可见性(可见性就是在获得属性值时,总能保证是最新值的特性)。

线程持有者属性

    //当前线程持有者标识
    private transient Thread exclusiveOwnerThread;

该属性是Thread类型的,标识了锁的当前持有线程。

ReentrantLock中的队列使用

​ ReentrantLock中的队列是FIFO(先进先出),为了实现公平锁,保证线程的线程的加锁顺序,同时也是存储元素,那么这个队列的数据结构是怎样的呢?

​ 在ReentrantLock中,定义了一个Node类,用来表示线程,同时也是链表的组成元素,Node的prev属性指向前一个节点(代表前一个进入队列的线程),next属性指向后一个节点(代表后一个进入队列的线程),这种方式形成了一个链表;AQS还维护了Node类型的head属性和tail属性,默认为null,分别表示头结点和尾节点,这两个属性为了让在后续逻辑中,能够很轻易的拿到头和尾节点,做出逻辑处理和判断。

以下是Node类的核心属性:

    //指向前一个节点   
    volatile Node prev; 

    //指向后一个节点   
    volatile Node next; 

    //指向当前节点表示线程    
    volatile Thread thread; 

    /*
      等待状态,针对 ReentrantLock ,共有3种
      0:初始化默认状态或者是无效状态,即在成员变量定义int类型默认为0,或者表示已解锁
      -1(SIGNAL):标记当前结点表示的线程在释放锁后需要唤醒下一个节点的线程,以当前值来标识是否要进行唤醒操作
      1(CANCELLED):在同步队列中等待的线程未正常结束(发生中断异常或者其它不可预知的异常),标记为取消状态
     */
    volatile int waitStatus; 

Demo&原理解析

非公平锁-lock()方法

Demo

    public void testReentrantLock() {    
     //多个线程使用同一个ReentrantLock对象,上同一把锁
      Lock lock = new ReentrantLock();      
      lock.lock();  
      System.out.println("处理");
      lock.unlock();    
    }

详尽原理

阅读详尽原理前,为加强理解,请对照源码阅读(本文末尾附带中文注释可运行源码链接)

​ 进入lock方法后,就先调用cas方法抢占锁(将state从0修改为1),不管是否有线程在排队;如果修改成功,则更新当前线程持有者属性为当前线程,如果修改不成功,则调用acquire方法;

    final void lock() {    
        //直接使用cas原子方法,如果state为0则修改为1,而不乖乖去FIFO(先进先出)队列排队    
        if (compareAndSetState(0, 1))        
            //如果上锁成功,将锁持有者修改为当前线程对象,上锁成功              
            setExclusiveOwnerThread(Thread.currentThread());   
        else        
            //如果失败,则执行以下逻辑(包括判断是否是线程重入、再次尝试上锁、阻塞线程、被唤醒获取锁等逻辑)   
           acquire(1);
    }

​ 进入acquire方法后,再尝试获取锁,如果再获取失败则初始化代表当前线程的节点,加入到队列当中去并阻塞,直到被唤醒(这里之所以要尝试2次获取动作,是为了充分发挥非公平锁性能优势)

    public final void acquire(int arg) {
        // 1.tryAcquire: 再次尝试上锁,true:标识上锁成功;false:标识上锁失败
        if (!tryAcquire(arg) &&
                /*
                 2.addWaiter:将线程封装成节点,放入FIFO队列中;
                 3.acquireQueued:
                 自旋获取锁,如果再次尝试获取锁失败,则阻塞线程;
                 等待队列中的前一个线程解锁后,唤醒本线程,成功获取锁
                 */
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //将线程的状态置为中断(该代码的作用在acquireQueued里讲)
            selfInterrupt();
    }

​ 进入1.tryAcquire后,如果此时无线程持有锁,则再次尝试获取锁:1.如果获取失败,进入队列;2.否则返回获取成功;如果有线程持有锁,则判断是否是当前线程持有锁:1.如果是,则累加重入数;2.如果不是,进入队列

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

    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()) {
            //之所以使用不支持原子性的操作进行赋值,是因为只有当前拥有锁的线程才能修改这个state,所以不会发生其他线程修改
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

​ 进入2.addWaiter后,创建代表当前线程的节点并追加链表末尾

    /**
     * 描述:初始化节点并追加链表末尾
     *
     * @param mode 标识当前线程的节点是独占模式还是共享模式,对代码逻辑没有实际意义
     * @return 代表当前线程的节点
     */
    private Node addWaiter(Node mode) {
        //初始化链表中的对象NOde,代表当前线程
        Node node = new Node(Thread.currentThread(), mode);

        Node pred = tail;
        //如果链表已经初始化(最后一个节点不为空),则直接将当前节点放在尾节点的后面接口
        if (pred != null) {
            //将node的prev属性赋值为之前链表的尾结点
            node.prev = pred;
            //  使用原子方法,tail变量赋值成node节点
            // (注:这里只修改了记录了尾节点的变量,并没有修改链表节点间的关联关系)
            if (compareAndSetTail(pred, node)) {//#1
                //将之前链表的尾结点的next属性赋值为node节点(这里之所以没有使用cas原子方法是因为其它线程想要修改t的next属性都必须成功获取t,而t是只有在t所代表节点是尾节点的那个时间点,成功执行compareAndSetTail(t, node)的线程才能够拥有的)
                pred.next = node;
                return node;
            }
        }
        //如果链表还没有初始化或者因为锁竞争激烈,#1处的compareAndSetTail执行失败,将会对链表进行初始化或者自旋直到compareAndSetTail执行成功
        enq(node);
        return node;
    }
    /**
     * 如果链表还没有初始化或者因为锁竞争激烈,#1处的compareAndSetTail执行失败,将会对链表进行初始化或者 自旋 直到compareAndSetTail执行成功
     */
    private Node enq(final Node node) {

  • 剩余80%的内容,添加本人QQ(验证信息填 文章标题 ),发送6.9元红包后即可查看
  • 若不没有及时回复,也可直接访问链接进行购买:;

深度解读 ReentrantLock底层源码_第1张图片