Java Lock源码解读

这篇具有很好参考价值的文章主要介绍了Java Lock源码解读。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一,概述

多线程问题本质是多个线程共同访问了同一块内存,导致该内存状态不确定而产生了一系列问题。concurrent包中提供的Lock类本质是对线程对象进行监督、排队,调度,确保lock只能有一个线程或共享线程成功返回,否则阻塞或取消或超时取消的策略。笔者将在本文对Lock类设计作一个详细的解读。

在解读过程中,首先会对Lock类实现进行代码阅读,并记录至标题二中,最后尝试从实现中抽象出架构设计,并绘制类图,时序图,活动图等,记录于标题三中。

二,实现解读

1,AbstractOwnableSynchronizer

Java Lock源码解读,java,开发语言

其中的继承关系如下:

Java Lock源码解读,java,开发语言

可以看到,此类是所有同步器的抽象父类,锁的粒度是线程对象,其中虽说是抽象类,但没有抽象方法,且提供的独占线程set或get方法均被final修饰,子类无法修改。

2,AbstractQueueSynchronizer

抽象同步队列,从注释可能看出,这是所有同步器类的基础实现,子类只能实现tryAcquire、tryRelease、tryAcquireShared、tryReleaseShare、isHeldExclusively方法,这些具体的实现稍后再谈。该基类中,维护了一个等待队列,可用来实现FIFO的阻塞锁或相关同步器,也提供了单个原子int来表示状态,而所谓的状态state,即是当前是否有线程占用,通过getState,setState,compareAndSetState方法来原子更新int值,其值本质是当前资源被锁所成功获取的次数,为0代表没有被线程独占,否则需根据策略去获取资源,可能是自旋或其他方式,仍稍后再谈。最后,核心提供了acquire方法,此方法签名如下:

Java Lock源码解读,java,开发语言

具体的实现稍后再谈。

(1)CLH Nodes

等待队列是“CLH”(Craig、Landin和Hagersten)锁定队列的变体。CLH锁通常用于自旋锁。相反,我们通过包括显式(“prev”和“next”)链接和一个“status”字段来使用它们来阻止同步器,该字段允许节点在释放锁时向后续节点发送信号,并处理由于中断和超时而导致的取消。状态字段包括跟踪线程是否需要信号的位(使用LockSupport.unpark)。尽管有这些添加,我们仍保留大多数CLH局部属性。

Java Lock源码解读,java,开发语言

如果要进入CLH lock,需要原子性地设置为新的tail节点。而对于排出CLH,则需要将head设置为下一个符合条件的waiter node,使其变为first。即同步队列采用双链表的形式实现,具体地,比如插入,或已经取消的node,则会调用cleanQueue方法显示清除。CLH的head节点是一个伪节点,因为大多数情况是不存在抢占的,因此设置head被认为是徒劳。直到发生抢占,便会构造出一个头指针和尾指针。接下来,我们看一下队列的维护实现。

Java Lock源码解读,java,开发语言

双链表节点,提供next、prev、waiter、status属性,并且status、next、prev等都通过unsafe原子式地写入。其有三个内部子类实现,如下,可以理解为独占节点、共享节点以及条件节点。

Java Lock源码解读,java,开发语言

Java Lock源码解读,java,开发语言

以上是同步队列数据结构,接下来看下如何维护。

(2)acquire

Java Lock源码解读,java,开发语言

Java Lock源码解读,java,开发语言

Java Lock源码解读,java,开发语言

Java Lock源码解读,java,开发语言

Java Lock源码解读,java,开发语言

通过对acquire方法详细阅读,总结一下:

当前线程尝试去acquire时,需判断有无node(这个可以理解为node条件,如果没有则通过share参数创建当前节点,如独占节点或共享节点),真正能让当前线程阻塞的是同步队列是否为空,如head节点是否存在。如果存在head节点,且当前是first节点,则去尝试获取一次,如果成功则将当前firs节点设置为head节点,否则自旋等待,自旋次数是等待状态下循环次数,如果没有设置等待状态且自旋次数未计算,则需计算然后进入阻塞,等待unpark唤醒此节点,重新上述流程。

(3)release

如(2)所述,WAITTING状态下的node需通过park方式进入阻塞,那么在哪被唤醒呢?答案就在release中。

Java Lock源码解读,java,开发语言

很简单,由子类决定是否释放,如果是否则通过头节点去通知下一个first节点,

Java Lock源码解读,java,开发语言

从头节点向后遍历,遇到合法的node后,将状态原子式的设置为非等待,然后唤醒等待节点的thread,即waiter所指。

(4)总结

由此可见,通过AQS的同步队列维护,可以实现大部分锁的基本操作,只需合理的重写AQS提供的抽象方法,即可创建出诸如condition、可重入锁、可重入读写锁等业务逻辑锁。下面,通过举例实现,如ReentrantLock的实现,来进一步熟悉AQS。

3,ReentrantLock

Java Lock源码解读,java,开发语言

通过参数fair决定创建公平sync或非公平sync,重点关注Sync,我们看一下

(1)Sync

Sync直接继承AQS,并且提供两个子类,

Java Lock源码解读,java,开发语言

 tryLock

tryLock是Sync的直接简单实现,尝试去获取锁,如果无法获取,直接返回false,否则返回true,非阻塞。实现如下,首先获取当前AQS的state,如果是0则未被占有,然后原子式的去设置state为1,成功后设置当前独占线程,否则返回false;如果state不为0,判断是否是当前线程独占,是则判断下是否重入溢出,然后返回true。实现很简单,适用于并发极其少的情况。

Java Lock源码解读,java,开发语言

这和tryRelease是配套的,看下源码如下。很简单,读者可自行理解。

Java Lock源码解读,java,开发语言

lock

Java Lock源码解读,java,开发语言

Sync提供了initialTryLock抽象方法,具体由子类实现,如果返回false,则调用acquire方法,参数为1,

Java Lock源码解读,java,开发语言

此时来到AQS中acquire,只有子类tryAcquire返回false,进入acquire核心方法,参数

node null、shared 非共享、interruptible 中断不取消、timed 非超时方式、time OL永远阻塞等待。

在此,先往回看initialTryLock定义。

Java Lock源码解读,java,开发语言

即,当返回false,代表不需要阻塞地去抢占锁,否则抢占。

而如何释放呢?看下unlock。

unlock

Java Lock源码解读,java,开发语言

直接调用AQS的release方法,不再赘述。

Java Lock源码解读,java,开发语言

接下来,我们看Sync的子类如何实现initialTryLock方法和tryAcquire方法来实现公平与否的吧。

(2)FairSync

Java Lock源码解读,java,开发语言

尝试获取锁的实现与tryLock一致,关键在于acquire中tryAcquire的实现差异,

Java Lock源码解读,java,开发语言

getState==0,Key去抢占锁,但需通过hasQueuePredecessors方法判断是否存在等待时间长于此线程的线程,如果有则不抢占,此处是实现公平锁的关键,否则仍通过cas操作去简单设置state为1,设置当前线程为独占线程结束,返回true;否则返回false,代表进入同步队列。

hasQueuePredecessors实现如下,

Java Lock源码解读,java,开发语言

只需判断存在firs节点指向的first线程,且first节点线程非当前线程。由此实现公平抢占(如果同步队列存在first节点,那就就不抢占吧)

(3)NonfairSync

非公平锁实现与FairSync大同小异,唯一的区别是tryAcquire,

Java Lock源码解读,java,开发语言

读到这读者应该明白了,只比公平锁少了hasQueuePredecessors判断。

接下来,我们看下基于AQS的condition如何实现的。

4,Condition

(1)介绍

condition类似于Object提供的wait和notify方法,用于阻塞/唤醒。condition接口提供了await/signal类方法。

(2)实现

实现只有ConditionObject,我们来看下。

Java Lock源码解读,java,开发语言

通过AQS.newCondition方法创建condition,其是AQS的内部类,持有AQS引用,condition节点是conditionNode,我们看下定义。

Java Lock源码解读,java,开发语言

提供了两个方法,block是如果未释放,则park,

isReleasable当status小于等于1或当前线程被中断,返回true。

接下来我们直接看核心方法的实现,其他方法读者可自行理解。

await

Java Lock源码解读,java,开发语言

先步步分解,

1)首先判断是否中断,抛出异常

2)创建Condition节点,调用enableWait方法进行设置,我们看下,

Java Lock源码解读,java,开发语言

可以看到,只有独占锁才支持condition,随后的操作是将当前线程设置到waiter中,原子地设置COND|WAITING状态,并且ConditionNode内部又维持了firstWaiter和lastWaiter指针,同样赋值,即可以多个线程Condition.await。接下来,获取到当前state,并且释放掉当前state,这样做的目的就是释放当前持有的锁,并且后续从await状态退出时,重新加锁。,即让AQS中下一个node唤醒,而await线程开始等待。

我们继续,从enableWait返回了state,进入如下while循环。canReacquire是简单判断node是否处在AQS队列中,而显然,新创建的ConditionNode不再AQS队列,进入while循环体。

Java Lock源码解读,java,开发语言

判断是否中断,如果中断就取消condition状态,break;

如果处在condition状态,加入到ForkJoinPool池中,是一种阻塞实现,通过内部调用block方法和isRelease方法,判断是否返回。而每次不能返回时,显示调用block方法,即ConditionNode的block实现,即park方式。

如果不出在condition状态,通过自旋方式,直到退出while循环。看到这,猜测signal核心就是将当前ConditioNode加入AQS队列中,这里canReacquire就返回true,进而退出while体了。

Java Lock源码解读,java,开发语言

接下来的部分就是调用acquire加入AQS队列中,复用node节点,此时已经清除了status。

signal

Java Lock源码解读,java,开发语言

实现如上,现判断是否加独占锁,随后如果有firstWaiter,则调用doSignal方法,注意参数all为false,

Java Lock源码解读,java,开发语言

正如上述笔者猜测,将first将入到AQS队列中,即实现了唤醒。但在加入之前,首先清除了COND状态,然后调用enqueue方法,我们看下实现。

Java Lock源码解读,java,开发语言

可以看到,将node插入AQS队列尾部,并且当status<0是,unpark等待线程。

问题来了,status在什么时候<0呢?此处大概率不为0而直接break,在unlock时,会去清除掉加入队列的ConditionNode,这个时候,通过signalNext方法去unpark ConditionNode.Waiter,从而实现await/signal功能。

具体使用方法参考对应注释。

Java Lock源码解读,java,开发语言

5,CountDownLatch

再来介绍一个简单的AQS队列的实现,倒计时。这主要是共享锁的实现例子。

(1)Sync

Sync实现如下,

Java Lock源码解读,java,开发语言

可以看到,通过Sync构造方法传入count,设置到state中。

重写了tryReleaseShared方法,主要是对count--,当count==0时返回true,这时releaseShared方法中就会调用signalNext,将阻塞的Node释放出。

为什么这么用呢?核心得看下CountDownLatch实现。

(2)await

Java Lock源码解读,java,开发语言

直接跟进

Java Lock源码解读,java,开发语言

很简单,通过acquire加入同步队列并等待。只有当count>=0是返回,这点可以通过Sync发现。

而只有tryAcquireShared返回大于0的值时,才会unpark等待线程,如下所示。

Java Lock源码解读,java,开发语言

因此,CountDownLatch需要显示调用countDown方法,才能唤醒等待线程,

Java Lock源码解读,java,开发语言

内部实现releaseShare,很简单,如Sync中重新所示,当count为0时采取释放节点。

三,抽象出架构设计

Java Lock源码解读,java,开发语言文章来源地址https://www.toymoban.com/news/detail-824384.html

到了这里,关于Java Lock源码解读的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手写java设计模式之单例模式,附源码解读

    在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处: 1、减少类的频繁创建,减少使用频繁使用new创建实例,减少GC压力。 2、某些应用场景下,使用单例模式,保证整个系统中只会创建一个类。 单例模式分两种:饿汉模式和懒汉模

    2024年04月29日
    浏览(48)
  • Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析

    List、Set、HashMap作为Java中常用的集合,需要深入认识其原理和特性。 本篇博客介绍常见的关于Java中HashMap集合的面试问题,结合源码分析题目背后的知识点。 关于List的博客文章如下: Java进阶(List)——面试时List常见问题解读 结合源码分析 关于的Set的博客文章如下: Jav

    2024年02月08日
    浏览(51)
  • Java进阶(List)——面试时List常见问题解读 & 结合源码分析

    List、Set、HashMap作为Java中常用的集合,需要深入认识其原理和特性。 本篇博客介绍常见的关于Java中List集合的面试问题,结合源码分析题目背后的知识点。 关于的Set的博客文章如下: Java进阶(Set)——面试时Set常见问题解读 结合源码分析 关于HaseMap的博客文章如下: Java进

    2024年02月06日
    浏览(54)
  • 从零开始学习 Java:简单易懂的入门指南之HashMap及TreeMap源码解读(二十四)

    Tip: 1.TreeMap添加元素的时候,键是否需要重写hashCode和equals方法? 此时是不需要重写的。 2.HashMap是哈希表结构的,JDK8开始由数组,链表,红黑树组成的。既然有红黑树,HashMap的键是否需要实现Compareable接口或者传递比较器对象呢? 不需要的。 因为在HashMap的底层,默认是利用

    2024年02月07日
    浏览(49)
  • Java进阶(ConcurrentHashMap)——面试时ConcurrentHashMap常见问题解读 & 结合源码分析 & 多线程CAS比较并交换 初识

    List、Set、HashMap作为Java中常用的集合,需要深入认识其原理和特性。 本篇博客介绍常见的关于Java中线程安全的ConcurrentHashMap集合的面试问题,结合源码分析题目背后的知识点。 关于List的博客文章如下: Java进阶(List)——面试时List常见问题解读 结合源码分析 关于的Set的博

    2024年02月06日
    浏览(61)
  • JSP 的本质原理解析:"编写的时候是JSP,心里想解读的是 java 源码"

    @ 目录 JSP 的本质原理解析:\\\"编写的时候是JSP,心里想解读的是 java 源码\\\" 每博一文案 1. JSP 概述 2. 第一个 JSP 程序 3. JSP 的本质就是 Servlet 4. JSP 的基础语法 4.1 在 JSP 文件中直接编写文字 4.2 在JSP中编写Java程序 % % 与 %! % 4.2.1 % % 4.2.2 %! % 4.3 通过JSP当中的 %= %向浏览器前端输入

    2024年02月01日
    浏览(136)
  • java 多线程Lock接口

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 Lock接口中常用的方法为 lock()  和  unlock() 想要使用Lock接口中方法要先创建Lock锁对象 run方法中用  ck.lock() 来上锁,用  ck.unlock() 来把锁归还。

    2024年02月08日
    浏览(36)
  • Java中Lock锁详解

      目录  一、Lock锁的基本使用  二、Condition类详解   三、进程的优先级 四、wait/join与sleep的区别: 在Java中, Lock 是一个接口,它提供了比 synchronized 更高级的线程同步机制。使用Lock接口可以创建更复杂和灵活的同步结构。 Lock接口的常用实现类有ReentrantLock和Reentra

    2024年02月07日
    浏览(21)
  • Java——》Synchronized和Lock区别

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月09日
    浏览(41)
  • Java多线程:Lock锁(未完待续)

    在Java中,Lock是一个接口,它提供了比synchronized更高级的线程同步机制。使用Lock接口可以创建更复杂和灵活的同步结构。 Lock接口的常用实现类有ReentrantLock和ReentrantReadWriteLock,它们提供了可重入的互斥锁和读写锁。 相比synchronized来实现同步,使用Lock实现同步主要有以

    2024年02月02日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包