首页 > 编程语言 ># ReentrantLock源码阅读

# ReentrantLock源码阅读

时间:2023-12-27 21:00:54浏览次数:32  
标签:return int lock ReentrantLock tryAcquire 源码 线程 阅读

ReentrantLock源码阅读

目录

本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分析,没有深入到一些native方法的实现。并且由于知识储备不完整,很可能出现疏漏甚至是谬误,欢迎指出共同学习

本文基于corretto-17.0.9源码,参考本文时请打开相应的源码对照,否则你会不知道我在说什么

简介

ReentrantLock是可重入独占锁,所谓可重入指的是占有锁的线程继续在这个锁上调用lock直接加锁成功,当然,lock与unlock的调用次数最终数量要相等,否则不会释放锁。而不可重入锁则是lock成功后再lock就会被阻塞。ReentrantLock基于AQS(AbstractQueuedSynchronizer),详情请看AbstractQueuedSynchronizer源码阅读

例子

ReentrantLock使用比较简单,随便看看就行,比如实现一个线程安全的计数器:

public class Counter {
  private final Lock lock = new ReentrantLock();
  private int count;

  public void add(int n) {
    lock.lock();
    try {
      count += n;
    } finally {
      lock.unlock();
    }
  }
}

代码分析

类似在AQS一文中实现的Mutex,ReentrantLock的方法基本也是通过AQS的方法实现的,因此直接看基于AQS的私有静态内部子类Sync如何实现的。另外需要明确是的,ReentrantLock是独占锁,因此可以忽略共享模式的相关方法,如tryAcquire

ReentrantLock支持公平锁和非公平锁。至于公平与否的概念,详情可见我AQS源码阅读的文章,我定义了三个公平级别,ReentrantLock支持的公平锁为强公平级别,支持的非公平锁为于弱公平级别。

ReentrantLock的内部静态类FairSync和NonfairSync分别用于支持公平和非公平锁,两者都从Sync类派生而来,因此先介绍Sync这个类的核心方法。

Sync.tryLock

final boolean tryLock() {
  Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  } else if (getExclusiveOwnerThread() == current) {
    if (++c < 0) // overflow
      throw new Error("Maximum lock count exceeded");
    setState(c);
    return true;
  }
  return false;
}

tryLock功能是尝试加锁,返回加锁是否成功,成功的话重入次数+1,另外这个tryLock是非公平的,因为它没有检测等待队列是否有线程在等待,而是直接尝试修改state加锁。

读过AQS那篇的话,这个方法读起来可谓是毫无压力的,ReentrantLock其他方法读起来同样也很简单(即使没仔细研究过AQS读这个其实也不难)。要注意的是AQS.state的类型是int,一直lock而不unlock的话会造成int溢出,直接抛一个Error。

Sync.initialTryLock

abstract boolean initialTryLock();

initialTryLock的功能同样是尝试加锁,在lock中的开头调用一次。由于公平与否的区别在于尝试加锁前有没有检查等待队列,因此提取这个方法留给子类实现。后面会介绍实现。

Sync.lock

final void lock() {
  if (!initialTryLock())
    acquire(1);
}

lock的功能就是字面意思:加锁。实现很简单,尝试加锁失败就通过acquire获取锁。

Sync.tryRelease

protected final boolean tryRelease(int releases) {
  int c = getState() - releases;
  if (getExclusiveOwnerThread() != Thread.currentThread())
    throw new IllegalMonitorStateException();
  boolean free = (c == 0);
  if (free)
    setExclusiveOwnerThread(null);
  setState(c);
  return free;
}

tryRelease是重写AQS的方法,功能就是尝试释放锁。如果不是持有锁的线程尝试释放锁则直接抛异常,否则就将state(重入数)减去相应的次数。

上面几个就是Sync的核心方法。还记得AQS实现独占锁要实现的三个方法吗?tryAcquiretryReleaseisHeldExclusively(这个实现很简单,不展示了),其中tryAcquire同样因为公平与否的特性,交给子类实现。综上,子类需要实现tryAcquireinitialTryLock两个方法:

NonFairSync.initialTryLock

final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    if (compareAndSetState(0, 1)) { // first attempt is unguarded
        setExclusiveOwnerThread(current);
        return true;
    } else if (getExclusiveOwnerThread() == current) {
        int c = getState() + 1;
        if (c < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(c);
        return true;
    } else
        return false;
}

嗯,看着有点眼熟呢,是的,跟Sync.tryLock几乎毛区别没有...不知道为什么不直接调用tryLock

NonFairSync.tryAcquire

protected final boolean tryAcquire(int acquires) {
  if (getState() == 0 && compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
  }
  return false;
}

也非常简单呢,不过注意lock和acquire的区别,lock可能是让state从0到1,从1到2...acquire就是纯纯的从0到1,也就是acquire的语义是本来线程是不占有锁的,现在要用acquire获取到锁,所以是从0到1。这里个函数很简单,但在这啰嗦了点,算是闲来无事突发的一些感悟。

FairSync.initialTryLock

final boolean initialTryLock() {
  Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  } else if (getExclusiveOwnerThread() == current) {
    if (++c < 0) // overflow
      throw new Error("Maximum lock count exceeded");
    setState(c);
    return true;
  }
  return false;
}

这个函数与NonFairSync.initialTryLock的区别只有:state从0到1之前先检查一下有没有正在等待队列的线程,以实现强公平。

FairSync.tryAcquire

protected final boolean tryAcquire(int acquires) {
  if (getState() == 0 && !hasQueuedPredecessors() &&
    compareAndSetState(0, acquires)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
  }
  return false;
}

这个函数与NonFairSync.tryAcquire的区别只有:state从0到1之前先检查有没有比当前线程等待更久的线程。因为tryAcquire可能会在好几个时间点调用(线程进入等待队列之前,线程进入等待队列之后),所以使用hasQueuedPredecessors来检查,这个函数在以下两种情况下返回true:

  • 当前线程没进入阻塞队列,且队列不为空
  • 当前线程已经在阻塞队列中,但前面有更靠近队头(等待时间更长)的线程

这两种情况下都得获取锁失败,以实现强公平。

至于ReentrantLock其他的方法实在没必要解析了,比如:

public void lock() { sync.lock(); }
public boolean tryLock() { return sync.tryLock(); }
public boolean hasWaiters(Condition condition) {
  if (condition == null)
    throw new NullPointerException();
  if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
    throw new IllegalArgumentException("not owner");
  return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

是吧...

参考链接

「博客园」AbstractQueuedSynchronizer源码阅读

标签:return,int,lock,ReentrantLock,tryAcquire,源码,线程,阅读
From: https://www.cnblogs.com/nosae/p/17931415.html

相关文章

  • 【C语言数据结构】对Lua Table源码的一次劣质学习
    /*new_key*/KLcBoolKLcmCreateMapKeyValue(KLCMAP_PTRpTag,KLCTVALUE_PTRpKv){ KLcBoolkbRet =KL_FALSE; KLcBoolkbIsKvLegal =KL_FALSE; DWORDdwInsertPos =0; DWORDdwFreePos =0; DWORDdwCollisionPos =0; KLCTVALUE_PTRptMainNo......
  • Java线程池ThreadPoolExecutor源码解析
    Java线程池ThreadPoolExecutor源码解析1.ThreadPoolExecutor的构造实现以jdk8为准,常说线程池有七大参数,通常而言,有四个参数是比较重要的publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,lon......
  • AbstractQueuedSynchronizer源码阅读
    AbstractQueuedSynchronizer源码阅读目录AbstractQueuedSynchronizer源码阅读例子Overview-CLH锁Overview的剩余内容代码分析cleanQueueacquireacquire总结acquire共享模式补充release内部类ConditionObject参考链接本人的源码阅读主要聚焦于类的使用场景,一般只在java层面进行分......
  • 阅读笔记二
    相较于一般的技术类书籍,这本书的厚度属于偏薄的。而其内容所涵盖的又比较多,从技艺,思想和以COLA作为范例的实践,三个角度去分享自己的经验。这就势必使得这本书不会纠结于技术细节。技艺篇中,作者从最基础的命名开始,延伸到规范、函数、设计原则、设计规范以及模型和DDD,从细部到大......
  • 2023年秋季个人阅读计划9
    《见微知著:从软件实践到软件工程》读后感在信息时代,软件工程的重要性日益凸显。作为IT学生,我深知掌握软件工程知识对于未来的职业生涯至关重要。最近,我阅读了《见微知著:从软件实践到软件工程》这本书,它以酒店信息管理系统的开发为例,深入浅出地介绍了软件工程的全过程。软件工程......
  • ThreadLocal底层源码解析
    ThreadLocal底层源码解析ThreadLocal:顾名思义的意思是本地线程或者局部线程的意思,其真正含义是希望多个线程之间拥有自己的局部变量,多个线程间拥有自己的私人变量,在多线程间不被共享,被线程单独享用,这就是ThreadLocal设计之初的原衷因此,无论是操作系统级别还是编程语言......
  • openjdk源码-java是如何执行shell命令的
    一般我们在java中调用shell脚本的方式如下publicintexecuteLinuxCmd(Stringcmd){LOGGER.info("cmd:{}",cmd);Runtimerun=Runtime.getRuntime();try{Processprocess=run.exec(cmd);InputStreamin=proce......
  • 12.26阅读笔记
    读《需求工程——软件建模与分析》有感今天大致的看了一下这本书,对软件需求分析有了初步的了解,我认为学习软件需求分析需要掌握的内容主要包括五个方面:需求基础与过程、需求获取、需求分析、需求的文档化和验证、需求管理与工程管理。一、需求的基础与过程这一部......
  • APP开发详解:数字药店系统源码
    数字药店系统的兴起,不仅为消费者提供了更加便捷的购药体验,也为药店管理和药品销售带来了全新的机遇。一、明确系统的基本功能:1.用户注册与登录2.药品浏览与搜索3.购物车与结算。4.在线支付与订单管理二、开发环境与技术栈选择前端开发环境通常使用React、Vue或Angular等流行的前端......
  • 大语言模型生成模型的源码结构复习
    modeling_gpt2.py:1099iflabelsisnotNone:#movelabelstocorrectdevicetoenablemodelparallelismlabels=labels.to(lm_logits.device)#Shiftsothattokens<npredictnshift_logits=lm......