【JDK源码分析】通过源码彻底理解ReentrantLock显示锁

前言
ReentrantLock和synchronized一样是一个可重入的互斥锁,但ReentrantLock功能更强大,它提供了非公平和公平两种锁争用策略供使用者选择,而synchronized只有非公平一种。ReentrantLock提供了可中断的锁等待机制以及可用于多组线程需要分组唤醒的条件。

类图
下面是ReentrantLock的类图,内部抽象类Sync继承了AbstractQueuedSynchronizer(以下简称AQS),公平锁FairSync、非公平锁NonfairSync继承了抽象类Sync。
ReentrantLock

源码
ReentrantLock类属性和构造器
先看ReentrantLock的属性,属性只有sync,可见由它实现了整个类的功能。公平锁是所有线程都按FIFO的方式进行,获取锁释放锁的顺序进行;而非公平锁则是在锁释放的时候,不会限制新来的线程进行锁的争用。

复制代码
private final Sync sync;

//默认构造器,生成的是公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
// 有参构造器,形参fair为true则构造公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}   

复制代码
ReentrantLock类主要方法
下面以ReentrantLock的主要方法的调用过程来一步步分析源码

  1. lock 方法
    lock方法调用了内部类Sync的lock方法

    public void lock() {
    sync.lock();
    }
    lock方法为抽象方法

    abstract void lock();

    我们先看lock方法的非公平实现

1.1 lock方法的非公平实现
lock方法大量使用了其祖父类AQS中的方法,这里补充几点:

AQS有个state变量,用于表示锁状态,为0表示处理无锁状态,>0表示处理有锁状态;
compareAndSetState为AQS提供操作state变量的CAS原子方法;
关于详细的AQS的源码理解,可以查看本人上一篇博客,本文将不再赘述AQS中的源码分析。
final void lock() {
// 原子操作state变量,当state为0时将其置为1
if (compareAndSetState(0, 1))
// 成功将其置为1表示
// 将当前线程设置为独占状态
setExclusiveOwnerThread(Thread.currentThread());
else
// CAS操作state变量失败,表示出于有锁状态
acquire(1);
}
acquire方法为AQS的方法,但其调用的tryAcquire则由NonfairSync实现

public final void acquire(int arg) {
    //当tryAcquire返回false时,执行acquireQueued方法,它是将当前线程加入到队列中等待锁释放
    // acquireQueued在AQS中实现,主要逻辑是将当前线程封装成一个对象,加入到同步队列等待锁释放,然后再争用锁
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire方法,获取锁的方法

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

    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前锁状态
        int c = getState();
        // 为0表示出于无锁
        if (c == 0) {
            // 比较并交换锁的值
            if (compareAndSetState(0, acquires)) {
                // 设置当前线程为独占状态
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 如果当前线程和独占锁的线程是同一个线程,也就是重入
        else if (current == getExclusiveOwnerThread()) {
            //重入时将锁状态量加1
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        // 出于有锁状态时,直接返回false,表示该线程获取锁失败
        return false;
    }

1.2 lock方法的公平实现
现在来看公平锁的lock方法

    final void lock() {
        // 还是调用的祖父类的acquire方法
        acquire(1);
    }

祖父类的acquire调用的公平锁的tryAcquire实现,细心的人可能一眼就会发现和非公平锁的获取锁实现的唯一区别就在获取锁之前判断之前是否已经线程在等待锁释放

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
             // hasQueuedPredecessors 用来判断是否有其它线程在等待锁释放
             // 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;
    }****

至此lock方法的公平和非公平实现已完毕。

  1. unlock 方法
    unlock方法是二种策略锁共用的,它也是通过调用的AQS的release方法完成锁释放的

    public void unlock() {
    sync.release(1);
    }
    其它方法比较简单,这里就不细说了。

总结
ReentrantLock一般的使用场景是synchronized不能满足我们的功能需求时才使用;
一般在使用的时候我们使用非公平锁,公平锁比非公平锁的吞吐量明显要低很多,这是因为公平锁在新的线程过来的时候都要去检查同步队列是否有等待锁的线程。

原文地址:http://blog.51cto.com/13883927/2149654

时间: 2024-08-02 23:32:30

【JDK源码分析】通过源码彻底理解ReentrantLock显示锁的相关文章

【JDK源码分析】通过源码分析CyclicBarrier

前言 CyclicBarrier它是什么?一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点.类似于朋友之间联系要在中午聚个会,几个朋友全部到齐后才开始喝酒吃菜. 源码 CyclicBarrier属性和构造器 public class CyclicBarrier { // 互斥锁 private final ReentrantLock lock = new ReentrantLock(); // 条件等待 private final Condition trip = lock.new

通过源码分析MyBatis的缓存

看了通过源码分析MyBatis的缓存这篇文章后,自己跟着源码过了一遍,对mybatis的一级缓存和二级缓存有了更清楚的认识. 一级缓存是SqlSession级别的,同一个sqlSession在第二次执行一个相同参数的select语句并且第一次执行后没有对数据进行更新,就会直接从缓存取值,而不再进行查找.通过阅读源码了解了这个过程,首先org.apache.ibatis.session.defaults.DefaultSqlSession.select(String, Object, RowBou

通过源码了解ASP.NET MVC 几种Filter的执行过程

一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源码是个很好的习惯,它不只停留在知道怎么用的阶段,而是让我们知道一系列的为什么,为什么这样设计,为什么这样使用....很多朋友应该看过<asp.net x 框架揭秘>这本书,确实不错,特别是边看源码边看书,可以有不小的收获.Ok,我不是大神,我只是心血来潮想看一下源码! 二.几种常见的Filter

MYSQL Study案例之--通过源码安装Mysql-5.6

MYSQL  Study案例之--通过源码安装Mysql-5.6 系统环境: 操作系统:RedHat EL6 DB Soft:  Mysql 5.6.4-m7 1.系统环境 [[email protected] Packages]# uname -a Linux rh6 2.6.32-358.el6.x86_64 #1 SMP Tue Jan 29 11:47:41 EST 2013 x86_64 x86_64 x86_64 GNU/Linux [[email protected] Packag

Ubuntu通过源码编译安装Octave 4.0

本教程/笔记,用于指导在Ubuntu及其他Linux系统上如何通过源码安装Octave. Octave简介 Octave是GNU旗下代替matlab的数学工具软件,语法与matlab高度兼容,并且支持一些独有的更清晰更符合Linux社区习惯的语法.虽然在一些具体工具包和部分特性和效率上不如matlab,但是对于一般用户,它是matlab的一个有效的合法的免费的替代工具. 3.8版本之前官方没有GUI界面,让想使用GUI,必须下载第三方的工具(如qtoctave). 3.8版本加入了实验性的GUI

Linux下通过源码编译GD库

因为之前都通过源码直接编译安装的lamp环境,所以好多扩展库都是没有安装的,突然现在要用到一个验证码类,imagecreate函数显示未定义,所以就来安装编译下GD库, 首先需要先安装 gd 前置库 : freetype ,jpegsrc,libpng. freetype wget "http://download.savannah.gnu.org/releases/freetype/freetype-2.4.0.tar.bz2" tar jxvf freetype-2.4.0.tar

Android 4.4 通过源码进行 root 操作

2019-05-28 关键字:rk root 笔者手里有一块运行着 Android4.4 操作系统的 rk3128 开发板.刚好还没 root 的,摸索了一方,找到一个可以成功 root 的方式,特此记录一下. 整个过程其实并没有网上说的这么复杂,就简单几步,照着做就好了. step 1 预置外部 su 程序进系统.这个 su 程序已经作为附件上传到网上了.下载链接如下 链接: https://pan.baidu.com/s/1dHBfJ-SipGx6rGbpuW-p6A 提取码: fda6 这

Linux下通过源码编译安装程序

ASK: Linux下通过源码编译安装程序(configure/make/make install的作用) configure Linux 平台有各种不同的配置,安装时需要通过 configure 来确定,如:编译器用的是 cc 还是 gcc.不同库文件所在目录等.执行 configure 后会生成 Makefile,Makefile 规定了用什么编译器.编译参数等信息. make 根据 Makefile 中规定的内容进行编译,生成的可执行文件放在当前目录或某个子目录. make install

通过源码理解Spring中@Scheduled的实现原理并且实现调度任务动态装载

前提 最近的新项目和数据同步相关,有定时调度的需求.之前一直有使用过Quartz.XXL-Job.Easy Scheduler等调度框架,后来越发觉得这些框架太重量级了,于是想到了Spring内置的Scheduling模块.而原生的Scheduling模块只是内存态的调度模块,不支持任务的持久化或者配置(配置任务通过@Scheduled注解进行硬编码,不能抽离到类之外),因此考虑理解Scheduling模块的底层原理,并且基于此造一个简单的轮子,使之支持调度任务配置:通过配置文件或者JDBC数据