ReentrantLock (重入锁) 源码浅析

一、ReentrantLock简介
ReentrantLock重入锁,顾名思义,就是支持重入的锁,它表示能够支持一个线程对资源的重复加锁;我们之前学习过Synchronized锁,它也是支持重入的一种锁,参考我的另一篇Synchronized 锁的实现原理与应用,Synchronized支持隐式的重入锁,比如递归方法,在方法运行时,执行线程在获取到了锁之后仍能连续多次地获取锁;ReentrantLock虽然不能隐式重入,但是获取到锁的线程多次调用lock方法,不会阻塞进入同步队列;除此之外在获取锁时支持公平或者非公平的选择。
二、主要成员和结构图
①、ReentrantLock关系图

②、Sync是ReentrantLock的内部类,继承AQS

③、FairSync公平的锁实现,也是ReentrantLock的内部类,继承Sync

④、NonfairSync非公平的锁实现,也是ReentrantLock的内部类,继承Sync

三、主要的方法
分析一些常用方法,不会介绍AQS,AQS的一些方法参考我的这一篇文章
①、构造方法,我们可以看出默认的无参是非公平锁,有参构造true表示公平,false表示非公平。

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

②、lock()获取锁,其实就是把state从0变成n(重入锁可以累加)。
实际调用的是sync的lock方法,分公平和非公平。

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

公平实现:FairSync,我们发现其实调用的是acquire,其实这个是AQS的acquire,然后aqs的acquire的方法里面又会调用tryAcquire方法,因为这个方法需要同步组件自己去实现,所以ReentrantLock里面重写了AQS的tryAcquire方法,所以我们获取到锁就会返回true,没有就会返回false;然后没有获取到锁的线程就交给AQS去处理。

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) {
                // hasQueuedPredecessors 判断队列还有没有其它node,要保证公平
                // 没有在用cas设置状态
                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;
        }
    }

非公平实现:NonfairSync,我们可以发现基本和公平一样,就没有hasQueuedPredecessors方法,没有遵循FIFO队列的模式,而是不管队列有没有node,自己都可以去获取锁,不需要排队

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        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()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

②、lockInterruptibly支持中断的获取锁,其实是调用了AQS的lockInterruptibly方法,在AQS方法里面又回去调用tryAcquire方法,这个方法在上面已经解释过了。

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

AQS的lockInterruptibly方法

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

③、tryLock(long timeout, TimeUnit unit),支持中断,并且在这个基础上增加了超时设置,其实也是调用了AQS的tryAcquireNanos方法,我们发现其实他也是调用的tryAcquire方法。

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

AQS的tryAcquireNanos方法

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

④、unlock释放锁,其实就是把state从n(可能发生了锁的重入,需要多次释放)变成0,这个不区分公平与非公平,首先其实也是调用AQS的release方法,然后AQS在调用子类Sync的tryRelease方法。

public void unlock() {
        sync.release(1);
    }

调用Sync的tryRelease方法

protected final boolean tryRelease(int releases) {
            // 获取锁的状态
            int c = getState() - releases;
            // 获得锁的线程才能释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 直到锁的状态是0,说明锁释放成功,因为有重入锁
            // 说明我们在一个线程里面调用几次lock,就要调用几次unlock,才能最终释放锁
            if (c == 0) {
                free = true;
                // 释放线程的拥有者
                setExclusiveOwnerThread(null);
            }
            // 设置锁的状态
            setState(c);
            return free;
        }

⑤、newCondition方法,创建一个newCondition。

public Condition newCondition() {
        return sync.newCondition();
    }

⑥、getHoldCount方法,获取当前线程获得锁的个数。

public int getHoldCount() {
        return sync.getHoldCount();
    }
        final int getHoldCount() {
            // 当前线程是否获取到锁
            return isHeldExclusively() ? getState() : 0;
        }

⑦、isHeldByCurrentThread方法,当前线程是否获取到锁。

protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don‘t need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

⑧、isLocked方法,是否有线程获取到了锁。

final boolean isLocked() {
            return getState() != 0;
        }

⑨、getOwner方法,获取取得锁的线程。
⑩、getQueueLength方法,获取同步队列的数量。

public final int getQueueLength() {
    // 从aqs的尾节点开始往前遍历,除去空节点(但是其实只有第一个节点是空节点),也就是thread != null
        int n = 0;
        for (Node p = tail; p != null; p = p.prev) {
            if (p.thread != null)
                ++n;
        }
        return n;
    }

四、总结

学习ReentrantLock,我们主要需要了解它,公平和非公平的实现,以及重入锁的获取与释放的流程,还有最重要的就是要了解AQS,这是实现重入锁的基础,因为ReentrantLock只是实现了AQS获取锁和释放锁制定的模板方法的语义,所以要理解ReentrantLock获取锁成功和失败具体都做了什么逻辑,和AQS的实现是离不开的。
可以参考我的这一篇AQS的文章
参考 《Java 并发编程的艺术》

原文地址:https://blog.51cto.com/14220760/2391256

时间: 2024-08-26 15:06:06

ReentrantLock (重入锁) 源码浅析的相关文章

【分布式锁】06-Zookeeper实现分布式锁:可重入锁源码分析

前言 前面已经讲解了Redis的客户端Redission是怎么实现分布式锁的,大多都深入到源码级别. 在分布式系统中,常见的分布式锁实现方案还有Zookeeper,接下来会深入研究Zookeeper是如何来实现分布式锁的. Zookeeper初识 文件系统 Zookeeper维护一个类似文件系统的数据结构 image.png 每个子目录项如NameService都被称为znoed,和文件系统一样,我们能够自由的增加.删除znode,在znode下增加.删除子znode,唯一不同的在于znode是

ReentrantLock 的公平锁源码分析

ReentrantLock 源码分析   以公平锁源码解析为例: 1:数据结构: 维护Sync 对象的引用:   private final Sync sync; Sync对象继承 AQS,  Sync  分为两个类:处理公平锁锁和非公平锁: FairSync   NonfairSync 具体的类图如下: 2:接下来重点分析AQS这个类:AbstractQueuedSynchronizer: AQS中的成员变量: private transient volatile Node head;   /

ReentrantLock 重入锁

一.Lock接口: 在Java SE 5之后,并发包中新增了Lock接口及相关实现类来实现锁功能. Lock接口和synchronized关键字实现锁的区别: (1)Lock接口需要显示的获取和释放锁,sychronized是隐式的获取和释放锁.也正因为如此,使得Lock接口拥有了锁获取与释放的可操作性.可中断的获取锁.超时获取锁 等多种synchronized关键字所不具备的同步特性. 例如:一个场景,先获取锁A,然后获取锁B,当B获得后,同时释放A,同时获取锁C.以此类推.这种场景synch

ReentrantLock(重入锁)简单源码分析

1.ReentrantLock是基于AQS实现的一种重入锁. 2.先介绍下公平锁/非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁.有可能,会造成优先级反转或者饥饿现象. 3.重入锁/不可重入锁 可重入锁:广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁. 不可重入锁

ReentrantLock重入锁

上次博客讲到了通过wait()方法和notify()方法来实现循环打印数字和字母得问题.其实使用重入锁也可以实现同样得功能,那么开始我们先通过源码来了解一下重入锁把. public void lock() { sync.lock(); } 首先它有一个lock()方法,它用来加锁,从代码中可以看到,它调用得是sync.lock()方法, public class ReentrantLock implements Lock, java.io.Serializable { private stati

ReentrantLock 重入锁(下)

前沿: ReentrantLock 是java重入锁一种实现,在java中我们通常使用ReentrantLock 和 synchronized来实现锁功能,本篇通过例子来理解下ReentrantLock使用以及什么是可重入锁. 理解可重入: 1. 锁机制是为了多线程并发访问共享资源情况下为保证线程的安全,和对资源的原子性操作, 举个例子: i=i+1;其实是三部操作首先将i读取到线程的临时区存放,然后加一操作,最后将结果写回去.所谓锁机制就是保证一段程序在某段时间只有一个线程执行. 2. 可重入

ReentrantLock获取锁、释放锁源码浅析

JUC包下的ReentrantLock是基于Aqs模板实现的,它区分公平锁和非公平锁,内部实现了两个同步器,本文关注非公平锁部分. 伪代码 我们先看两个伪代码: 1.获取锁 1 if(获取锁成功) { 2 return 3 } else { 4 加入等待队列 5 for(死循环) { 6 if (获取到锁) { 7 return 8 } 9 阻塞线程 10 } 11 } 我们看到,如果一次获取成功则结束,如果没有获取成功将进入循环中,并且当前线程阻塞直到被唤醒并且获取到锁才结束. 2.释放锁 1

ReentrantLock(重入锁)功能详解和应用演示

1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock.虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景. 2. ReentrantLock和synchronized的相同点 2.1 ReentrantLock是独占锁且可重入的 例子 public class Reentr

[图解Java]ReentrantLock重入锁

图解ReentrantLock 0. demo 我先给出一个demo, 这样大家就可以根据我给的这段代码, 边调试边看源码了. 还是那句话: 注意"My" , 我把ReentrantLock类 改名为了 "MyReentrantLock"类 , "Lock"类 改名为了"MyLock"类. 大家粘贴我的代码的时候, 把相应的"My"都去掉就好了, 否则会编译报错哦. import java.util.Sca