前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java-ReentrantLock-非公平锁源码分析

Java-ReentrantLock-非公平锁源码分析

作者头像
颍川
发布2021-12-06 15:53:18
2370
发布2021-12-06 15:53:18
举报
文章被收录于专栏:颍川颍川
说明

在了解AQS后,那应该怎么了解AQS的最佳实践那,我想再也没有Java官方的实践更加优秀了,这次我打算重新拿出系统源代码,并将其总结成一系列文章,以供将来查看.

本次准备分六篇文章用来分析基于AQS实现的类

  1. 第一篇(Java-ReentrantLock-非公平锁源码分析)
  2. 第二篇(Java-ReentrantLock-公平锁源码分析)
  3. 第三篇(Java-并发工具-CountDownLatch源码分析)
  4. 第四篇(Java-并发工具-Semaphore源码分析)
  5. 第五篇(Java-ReentrantReadWriteLock-读锁分析)
  6. 第六篇(Java-ReentrantReadWriteLock-写锁分析)

介绍

本篇文章为系列文章的第一篇,本篇文章介绍ReentrantLock(可重入锁)非公平锁代码实现,ReentrantLock是一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

什么是可重入锁

可重入锁指的是线程可以重复获取同一把锁.

概述

首先,我们从总体过程入手,了解ReentrantLock非公平锁的执行逻辑,然后逐步深入分析源代码。

  1. 我们知道AQS内部是对阻塞线程队列的维护.因此,框架不支持基于优先级的同步,同时也表明加入队列的线程是按照顺序获取执行时间的,如果我们顺序的加入阻塞队列,我们AQS实现的锁就是公平锁,这就像我们生活中排队结账,如果保证结账的公平性,那就是有序的排队,打破这种公平的状态方式就是插队.
  2. 首先ReentrantLock非公平锁当前线程会先通过compareAndSetState获取同步状态,如果获取成功,改线程就不会和AQS阻塞队列做任何交互,如果获取失败在重写的tryAcquire方法中,当前线程会和AQS首节点线程竞争获取state同步状态,如果这次失败了,当前线程节点才会去AQS排队

源码分析

基于上面提到的过程,让我们来看看源代码实现逻辑.首先,让我们看看如果创建非公平锁。

代码语言:javascript
复制
   //默认构造器 构建非公平锁
   public ReentrantLock() {
     sync = new NonfairSync();
   }
   //传值 构建 true 构建 公平锁,false 构建非公平锁     
   public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
   }

由上面代码可知,ReentrantLock提供了两个构造方法,默认无参构造器构建非公平锁

代码语言:javascript
复制
  // 非公平锁 为一个内部类 ,他继承 Sync 内部类
  static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    @Override
    final void lock() {
        //当前线程直接尝试设置state (当前语义就是获取锁)
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //如果失败执行 acquire (AQS提供独占模式)
            acquire(1);
    }
    //重写AQS tryAcquire
    @Override    
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
  }

由上面代码可知,NonfairSync继承SYNC,实现lock方法,通过lock方法可以知道,当加锁时,lock直接调用compareAndSetState设置当前线程,当执行失败的时候才执行AQS acquire方法. NonfairSync重写了tryAcquire,接下来上我们看下 tryAcquire的代码实现。

代码语言:javascript
复制
 //抽象内部类 继承 AQS
 abstract static class Sync extends AbstractQueuedSynchronizer {

    private static final long serialVersionUID = -5965833819987602667L;
    //声明抽象方法 lock 
    abstract void lock();
    //ReentrantLock state 当前语义 0 未有线程获取锁 state > 0 代表已经存在线程占用
    //执行不公平的tryLock。
    //Try Acquire是在子类中实现的,但两者都需要对trylock方法进行不公平的尝试。
    final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程
        final Thread current = Thread.currentThread();
        //获取当前状态
        int c = getState();
        //如果未有线程占用
        if (c == 0) {
          //尝试直接设置 state 状态 (这里是第二次尝试不参与AQS排队)      
          if (compareAndSetState(0,acquires)) {
            //如果设置成功 设置setExclusiveOwnerThread 为当前线程
            setExclusiveOwnerThread(current);
            return true;
          }
        }
        //这个判断就是可重入锁实现的支持
        //如果当前线程等于 getExclusiveOwnerThread ,标识线程已经获取锁
        else if (current == getExclusiveOwnerThread()) {
          //当前线程累加 acquires
          int nextc = c + acquires;
          //如果 nextc < 0 标识非法 state 状态
          if (nextc < 0) {
            throw new Error("Maximum lock count exceeded");
          }
          //设置state 由于是同一个线程操作(线程重新进入),不存在安全问题,
          //所以可以直接使用setState设置state 
          setState(nextc);
          return true;
        }
        //如果以上都没有设置成功
        //则返回失败 执行AQS排队机制
        return false;
    }
    //后续代码省略
 }

以上入口操作要说明三点 第一,lock获取锁失败后,通过AQS acquire 方法执行调用nonfairTryAcquire 再次尝试获取锁, 如果获取状态为0 则nonfairTryAcquire 会再次尝试直接设置(获取锁)setStae值,如果获取成功则不再执行AQS排队操作,如果获取失败则执行AQS排队.

第二,nonfairTryAcquire方法是支持可重入操作的,如果当前线程获取锁失败,会在判断当前线程是否等于获取到锁的线程,如果等于则当前线程在状态增加(+acquires),不在参与AQS排队

第三,我们通过观察可重入锁发现,每次可重入时当前状态递增,state 是int类型,所以由此推算可重入锁的递归深度是2147483648 也就是int值(2^31-1)范围

接下来,看看释放锁的代码:

代码语言:javascript
复制
   //
   abstract static class Sync extends AbstractQueuedSynchronizer {
        //省略部分代码

        //实现tryRelease
        @Override 
        protected final boolean tryRelease(int releases) {
            //当前状态减去releases(因为存在递归)
            int c = getState() - releases;
            //如果当前线程不等于获取锁的线程 则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();    
            boolean free = false;
            //如果状态等于0 则表示当前线程释放了锁资源
            //针对锁资源唤醒的优化 只有当前线程完全释放锁了才自行唤醒操作
            //频繁唤醒,会导致锁竞争增加,cpu资源浪费
            if (c == 0) {
                //free 设置为true,AQS代码研究是可以知道 tryRelease返回true
                //AQS会唤醒内部阻塞节点 竞争锁资源
                free = true;
                setExclusiveOwnerThread(null);
            }
            //设置状态
            setState(c);
            return free;
        }
        //省略部分代码
    }

释放锁代码比较简单,但是我们依然看见了锁资源的优化,就是只有当前线程完全释放锁资源后,才会让AQS唤醒后续节点,这样避免频繁唤醒线程,减少锁竞争

总结

以上是ReentrantLock的获取和释放源码分析,它的实现其实很简单,这些都因为它基于AQS实现,AQS已经帮我们实现了大多数功能,了解ReentrantLock源码实现能够让我们更加深入的了解AQS设计思想。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/04/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说明
  • 介绍
  • 什么是可重入锁
  • 概述
  • 源码分析
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档