带你看看Java的锁(一)-ReentrantLock
前言
AQS一共花了5篇文章,对里面实现的核心源码都做了注解 也和大家详细描述了下,后面的几篇文字我将和大家聊聊一下AQS的实际使用,主要会聊几张锁,第一篇我会和大家聊下ReentrantLock 重入锁,虽然在讲AQS中也穿插了讲了一下,但是我还是深入的聊下
PS:前面5篇写在了CSDN里面 懒得转过来来了,有兴趣的移步去看下吧
- Java-AQS同步器 源码解读1-独占锁加锁
- Java-AQS同步器 源码解读2-独占锁解锁
- Java-AQS同步器 源码解读3-共享锁
- Java-AQS同步器 源码解读4-条件队列上
- Java-AQS同步器 源码解读5-条件队列下
ReentrantLock简介
ReentrantLock中文翻译:重入锁。那具体重入是什么意思呢,如果看过前面几篇文章的人,应该了解一下,简答的说就是AQS同步器里面的State相当于一个计数器,如果某一个线程获取锁了以后再再次去获取锁,这个计算器State就会+1.后面的代码中会详细的描述到。
还有一个重要的点 就是lock和Condition的联合使用,ReentrantLock可以创建一个Condition,这个我在条件队列的文章中详细描述过。
Synchronized对比
ReentrantLock是一个排他锁,也就是同一个时刻只会有一个线程能获取到锁,这个主要利用的就是AQS的独占模式实现的。ReentrantLock能保证在多线程的情况下的线程执行安全,那就会想到Synchronized的关键字。Synchronized是JVM实现的,ReentrantLock是由JDK实现的,ReentrantLock相对于比较灵活,可以设置时间等待,线程中断,锁投票等,但是一定用完记得要在finally手动释放,Synchronized是JVM做自动释放锁的操作。
用法
/**
 * @ClassName ReentrantLockDemo
 * @Auther burgxun
 * @Description: 重入锁的Demo
 * @Date 2020/4/5 14:21
 **/
public class ReentrantLockDemo {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                reentrantLock.lock();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("获取锁的线程是:" + finalI);
                reentrantLock.unlock();
            }).start();
        }
    }
}
执行结果是:
org.example.ReentrantLockDemo
获取锁的线程是:1-开始执行
获取锁的线程是:1-执行结束
获取锁的线程是:2-开始执行
获取锁的线程是:2-执行结束
获取锁的线程是:4-开始执行
获取锁的线程是:4-执行结束
获取锁的线程是:3-开始执行
获取锁的线程是:3-执行结束
获取锁的线程是:5-开始执行
获取锁的线程是:5-执行结束
Process finished with exit code 0
从执行结果上 我们可以看到 一个线程执行完成释放锁后才能执行另外一个线程
源码分析
看完了上面的简介和用法,我们进入源码去分析看下 是怎么实现的
代码结构

方法分析
从UML类图上面我们可以看到 ReentrantLock有3个内部类,一个是抽象的静态类Sync还有2个实现了Sync的类一个是非公平锁的实现NonfairSync,还有一个是公平锁的实现FairSync
Sync
首先我们看下Sync这个抽象类
abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();
        /**
         * 非公平锁锁的tryAcquire的实现 AQS中tryAcquire方法的需要子类重写
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//获取当前线程
            int c = getState();//获取CAS的State值
            if (c == 0) {//如果等于0 说明可以获取锁
                if (compareAndSetState(0, acquires)) {//CAS的方式 修改State 状态
                    setExclusiveOwnerThread(current);//CAS修改成功后 设置当前线程为锁的持有者
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {//判断当前线程 是否是锁的持有者线程
                int nextc = c + acquires;//新增重入次数
                if (nextc < 0) // overflow  //如果小于0 说明是出问题了 抛出异常
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        /**
         * AQS 中方法的重写 释放资源
         */
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;// 算下 当前同步器中state的差值
            //确保释放和加锁线程是同一个  上一篇中我也提到过
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;//是否完全了释放资源
            if (c == 0) {//如果C的值是0 说明没有线程拥有锁了
                free = true;
                /*
                 * 设置拥有锁的线程为null 因为现在锁是没线程暂用的  如果不修改 下次别的线程去获取锁的会有这个判断
                 */
                setExclusiveOwnerThread(null);
            }
            setState(c);//修改 同步器的State 状态
            return free;
        }
        /**
         * 判断当前线程 是否是拥有锁的线程一致  重写了AQS的方法
         */
        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();
        }
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        /**
         * 获取当前重入锁的拥有者
         */
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        /**
         * 当前锁占用的State 数量 也就是重入了几次
         */
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
        /**
         * 重入锁 是否可以被占用  State等于0 别的线程才能来抢占到锁
         */
        final boolean isLocked() {
            return getState() != 0;
        }
    }
上面是我写了注解的Sync的整个类,一会儿我们挨个分析下,从继承关系 我们可以看到Sync是继承于我们的AQS的,所以里面很多底层的方法都是用的AQS里面的实现,所以说呀 理解了AQS 那整个JUC下的锁和各种线程安全的集合什么的 看源码都会轻松很多~
NonfairSync
//非公平锁
    static final class NonfairSync extends Sync {
        @Override
        void lock() {
            if (compareAndSetState(0, 1))//如果CAS能设置成功 说明能获取到锁 就设置当前线程为锁的owner  不公平的体现
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        /**
         * 尝试获取资源,复写的AQS类里面方法
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
FairSync
  //公平锁
    static final class FairSync extends Sync {
        @Override
        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 返回是否Sync中是否有在当前线程前面等待的线程 false没有
                 * 如果false 那就CAS 修改State 成功后更新当前线程是锁的拥有者线程
                 * */
                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;
        }
    }
非公平锁VS公平锁
什么是公平非公平
看了上面的代码  有人可能不明白什么叫做TMD公平!!!哈哈
具体的代码下文描述,我先举个小例子,让读者带入下:
话说大家在火车站排队买票,小明和小强放寒假了,都去火车站提前买票准备回家,他们俩到了车站一看,售票窗口就开了一个,而且前面有3个人在排队,那小明和小强没办法就只能排除在了队伍里面,等了5分钟终于排到了小强,小强刚买完票,突然看到同个宿舍的小张也来买票了,立马和小张说,来来来 这边 我这边可以买票,小张立马插队到了小强后面,强行去买了票,小明心里嘀咕说居然插队,素质真差,看你人高马大的 就算了吧!哈哈~
其实上面的类子中 公平和非公平的体现就是 卖票的窗口只有一个,就像获取独占锁,当有一个线程占有了锁,那其余的线程就必须在后面排队等待,就像买票一样,非公平的锁的实现就相当于插队,管你后面有没有人 我都要去尝试下买票
从上面的分析我们也能知道公平和非公平是指的是获取资源时候的行为。
ReentrantLock
ReentrantLock的构造函数
/**
    * 默认实现非公平锁
    */
   public ReentrantLock() {
       sync = new NonfairSync();
   }
   /**
    * 带参数的构造函数
    */
   public ReentrantLock(boolean fair) {
       sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
   }
从上面可以看到ReentrantLock默认使用的是一个非公平锁的实现
lock加锁方法
    /**
     * 加锁
     */
    @Override
    public void lock() {
        sync.lock();
    }
    /**
     * 加锁 响应中断
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    @Override
    /**
     * 只做一次获取锁的尝试  不会阻塞线程
     */
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    @Override
    /**
     * 只做一次获取锁的尝试  不会阻塞线程
     */
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
非公平的加锁

从代码中 我们看到lock方法调用的是Sync的lock方法,但是Sync中的lock方法是一个抽象方式是子类实现的
那我从NonfairSync类中找到了lock的具体实现,入下:
 void lock() {
         //如果CAS能设置成功 说明能获取到锁 就设置当前线程为锁的owner  不公平的体现
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
首先方法一开始先执行一个CAS修改State的操作,如果能够执行成功,说明当前的AQS同步器中的状态值是0,那么线程就可以占有锁,然后设置当前线程为锁的Owner线程.setExclusiveOwnerThread这个方法我在第一篇文章中也描述过,这个方法是在AQS的父类AbstractOwnableSynchronizer方法里面的!
如果执行失败,那么方法就执行acquire方法:
 /**
     * 此方法位于AQS中
     * 获取资源
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    /**
     * 此方法位于NonfairSync中
     * 尝试获取资源,复写的AQS类里面方法
     */
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
     /**
     * 此方法位于Sync中
     * 非公平锁锁的tryAcquire的实现 AQS中tryAcquire方法的需要子类重写
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();//获取当前线程
        int c = getState();//获取CAS的State值
        if (c == 0) {//如果等于0 说明可以获取锁
            if (compareAndSetState(0, acquires)) {//CAS的方式 修改State 状态
                setExclusiveOwnerThread(current);//CAS修改成功后 设置当前线程为锁的持有者
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {//判断当前线程 是否是锁的持有者线程
            int nextc = c + acquires;//新增重入次数
            if (nextc < 0) // overflow  //如果小于0 说明是出问题了 抛出异常
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
acquire方法位于AQS中 此方法会首先调用tryAcquire方法去尝试获取下锁,因为虽然lock方法一开始获取锁失败了,可能这边锁又被别的线程释放了,所以要再次尝试获取下锁,具体tryAcquire怎么做的 上面的代码中我已经描述的很清楚了~
公平的加锁

先看下代码:
     /**
     * 公平锁版本的加锁
     */
    void lock() {
        acquire(1);
    }
    /**
     * 此方法位于AQS中
     * 获取资源
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    /**
     * 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 返回是否Sync中是否有在当前线程前面等待的线程 false没有
             * 如果false 那就CAS 修改State 成功后更新当前线程是锁的拥有者线程
             * */
            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的时候,直接执行了acquire方法,而非和NonfairSync一样,有个CAS的尝试操作,这也是体现公平的一部分,和非公锁的方法一样的是这边的acquire方法也是在AQS上中的,但是调用的tryAcquire方法 是自己fairSync类自己实现的,而非调用的Sync的默认实现,这边唯一有区别的就是hasQueuedPredecessors 这个方法,hasQueuedPredecessors方法是获取当前Sync队列中是否还有别的等待线程,如果有 就算当前的状态State是满足条件的,也是要加入Sync等待队列中的,这个是acquireQueued方法里面做的事情,不清楚这个acquireQueued方法的,看看前面几篇文章,我就不再赘述了~
unlock解锁
@Override
    public void unlock() {
        sync.release(1);//调用的AQS里面的方法
    }
     /**
     * 此方法在AQS中
     * 释放当前资源
     * 唤醒等待线程去获取资源
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//说明释放资源成功
            Node h = head;//当前队列里面的head节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//通知阻塞队列里面的head的next节点去获取锁
            return true;
        }
        return false;
    }
     /**
     * 此方法在Sync中
     * AQS 中方法的重写 释放资源
     */
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;// 算下 当前同步器中state的差值
        //确保释放和加锁线程是同一个  上一篇中我也提到过
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;//是否完全了释放资源
        if (c == 0) {//如果C的值是0 说明没有线程拥有锁了
            free = true;
            /*
             * 设置拥有锁的线程为null 因为现在锁是没线程暂用的  如果不修改 下次别的线程去获取锁的会有这个判断
             */
            setExclusiveOwnerThread(null);
        }
        setState(c);//修改 同步器的State 状态
        return free;
    }
从源码上看 解锁的方法只有一个 因为公平锁和非公共锁 只是描述的是加锁的行为,解锁的行为其实都是一致的,都是释放当前线程占用State值,然后唤醒SyncQueue的头部Head节点的下一个节点去尝试获取锁!
有些没帖子上面的方法描述 请在前面AQS中的几篇文章中看下 都详细描述过~
总结
公平锁 VS 非公平锁
首先2者并没有好坏之分,是要根据对应的场景选择对应的锁技术
公平锁 则重的是公平性
非公平锁 则重的是并发性
非公平锁 是抢占式的,忽略了SyncQueue重其他的等待线程,线程在进入等待队列之前会进行2次尝试获取锁,这大大增加了获取锁的机会,这种好处体现在2个方面:
- 线程不必加入等待队列就可以获取锁,不仅免去了构造节点并加入队列的繁琐操作,同时也节省了线程阻塞的唤醒开销,线程阻塞和唤醒涉及到线程上下文的切换和操作系统的系统调用,是非常耗时的。在高并发的情况下,如果线程持有锁的时间非常短,短到线程入队阻塞的过程超过了线程持有并释放锁的时间的开销,那么这种抢占式的特性对并发的性能的提高会很明显
- 减少CAS竞争,如果线程必须要加入阻塞队列才能去获取锁,那么入队时的CAS竞争将变得异常的激烈,CAS操作虽然不会导致线程失败而挂起,但不断的失败重试导致对CPU的浪费是不能忽略的
Synchronized VS ReentrantLock
从整个文章的分析来看,ReentrantLock是比Synchronized更加的灵活的,
- ReentrantLock提供了更多 更全面的API 可以设置等待时间,可以中断方法等,还提供了Trylock等非阻塞的方法
- ReentrantLock还可以配和Condition一起使用,使得线程等待的时候更加灵活,可以设置不同的条件等待 等等
带你看看Java的锁(一)-ReentrantLock的更多相关文章
- 带你看看Java的锁(三)-CountDownLatch和CyclicBarrier
		带你看看Java中的锁CountDownLatch和CyclicBarrier 前言 基本介绍 使用和区别 核心源码分析 总结 前言 Java JUC包中的文章已经写了好几篇了,首先我花了5篇文章从源 ... 
- 带你看看Java的锁(二)-Semaphore
		前言 简介 Semaphore 中文称信号量,它和ReentrantLock 有所区别,ReentrantLock是排他的,也就是只能允许一个线程拥有资源,Semaphore是共享的,它允许多个线程同 ... 
- 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁
		问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ... 
- 一篇blog带你了解java中的锁
		前言 最近在复习锁这一块,对java中的锁进行整理,本文介绍各种锁,希望给大家带来帮助. Java的锁 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人 ... 
- Java多线程之ReentrantLock重入锁简介与使用教程
		转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html 我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoize ... 
- 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
		问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ... 
- java并发系列(三)-----ReentrantLock(重入锁)功能详解和应用演示
		1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock.虽然在性能上ReentrantLock和synchronize ... 
- Java多线程--锁的优化
		Java多线程--锁的优化 提高锁的性能 减少锁的持有时间 一个线程如果持有锁太长时间,其他线程就必须等待相应的时间,如果有多个线程都在等待该资源,整体性能必然下降.所有有必要减少单个线程持有锁的时间 ... 
- java synchronized和(ReentrantLock)区别
		原文:http://blog.csdn.net/zheng548/article/details/54426947 区别一:API层面 syschronized使用 synchronized即可修饰方 ... 
随机推荐
- 让ul li水平居中(任意删除li也能水平居中)
			HTML代码: <div class="box"> <ul class="button-ct"> <li></li&g ... 
- SpringCloud-Ribbon负载均衡机制、手写轮询算法
			Ribbon 内置的负载均衡规则 在 com.netflix.loadbalancer 包下有一个接口 IRule,它可以根据特定的算法从服务列表中选取一个要访问的服务,默认使用的是「轮询机制」 Ro ... 
- CKEditor与定制
			一 开始使用 官网 基本示例: 搭建服务器(这里使用apache) 下载standard的ckeditor解压放在apache的htdocs的目录下 在htdoc下面新建index.html,写入代码 ... 
- phpstudy之访问loaclhost显示目录
			phpstudy版本:phpstudy2018 具体操作: 当前版本的默认设置访问网站根目录是不会显示目录的,需要我们设置,其实也很简单,只需两步就可以搞定 1.找到phpstudy目录下的www文件 ... 
- 初始化 RESTful API 风格的博客系统
			作者:HelloGitHub-追梦人物 文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 在 HelloDjango 全栈系列教程的第一步--Django博客教程(第二版)中 ... 
- Spring5:Java Config
			@Configuration @Bean @ComponentScan @ImportResource 使用Java的方式配置spring,完全不使用spring配置文件,交给java来做! 两个注解 ... 
- MVC-基础01
			MVC体系结构将应用程序分成三个主要组件:模型(Model).视图(View).和控制器(Controller).在ASP.NET MVC应用程序中,数据操控的逻辑包含在Models文件夹下,数据的展 ... 
- SQL Server 之T-SQL基本语句 (1)
			花了一天的时间看完了一本<SQL必知必会>,举个范例,来总结一下零碎的知识点.一般关于数据库操作的项目都会涉及到数据库的基本查询语句.在这里面就主要讲解一些基本常用的sql使用方法. 注: ... 
- 解决IE升级后必须以管理员运行的问题
			很多网友可能都遇到过这样的问题,在ie升级后,无法打开,必须以管理员身份运行.今天我也遇到了这个问题.最终找到了解决办法. 1.Win + R 2.输入 regedit,定位到 HKEY_CURREN ... 
- pytorch 中序列化容器nn.Sequential
			按下图顺序搭建以及执行 
