热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

State实现锁的原理

这篇文章主要介绍“State实现锁的原理”,在日常操作中,相信很多人在State实现锁的原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操

这篇文章主要介绍“State实现锁的原理”,在日常操作中,相信很多人在State实现锁的原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”State实现锁的原理”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

今天我们就来聊一聊基于AQS实现的各种锁。

1 ReentrantLock

我们先来看一下UML类图:

State实现锁的原理

从图中可以看到,ReentrantLock使用抽象内部类Sync来实现了AQS的方法,然后基于Sync这个同步器实现了公平锁和非公平锁。主要实现了下面3个方法:

  • tryAcquire(int arg):获取独占锁

  • tryRelease(int arg):释放独占锁

  • isHeldExclusively:当前线程是否占有独占锁

ReentrantLock默认实现的是非公平锁,可以在构造函数指定。

从实现的方法可以看到,ReentrantLock中获取的锁是独占锁,我们再来看一下获取和释放独占锁的代码:

public final void acquire(int arg) {     if (!tryAcquire(arg) &&         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))         selfInterrupt(); }

独占锁的特点是调用上面acquire方法,传入的参数是1。

1.1 获取公平锁

获取锁首先判断同步状态(state)的值。

1.1.1 state等于0

这说明没有线程占用锁,当前线程如果符合下面两个条件,就可以获取到锁:

  • 没有前任节点,如下图:

State实现锁的原理

  • CAS的方式更新state值(把0更新成1)成功。

如果获取独占锁成功,会更新AQS中exclusiveOwnerThread为当前线程,这个很容易理解。

1.1.2 state不等于0

这说明已经有线程占有锁,判断占有锁的线程是不是当前线程,如下图:

State实现锁的原理

state += 1值如果小于0,会抛出异常。

如果获取锁失败,则进入AQS队列等待唤醒。

1.2 获取非公平锁

跟公平锁相比,非公平锁的唯一不同是如果判断到state等于0,不用判断有没有前任节点,只要CAS设置state值(把0更新成1)成功,就获取到了锁。

1.3 释放锁

公平锁和非公平锁,释放逻辑完全一样,都是在内部类Sync中实现的。释放锁需要注意两点,如下图:

State实现锁的原理

为什么state会大于1,因为是可以重入的,占有锁的线程可以多次获取锁。

1.4 总结

公平锁的特点是每个线程都要进行排队,不用担心线程永远获取不到锁,但有个缺点是每个线程入队后都需要阻塞和被唤醒,这一定程度上影响了效率。非公平锁的特点是每个线程入队前都会先尝试获取锁,如果获取成功就不会入队了,这比公平锁效率高。但也有一个缺点,队列中的线程有可能等待很长时间,高并发下甚至可能永远获取不到锁。

2 ReentrantReadWriteLock

我们先来看一下UML类图:

State实现锁的原理

从图中可以看到,ReentrantReadWriteLock使用抽象内部类Sync来实现了AQS的方法,然后基于Sync这个同步器实现了公平锁和非公平锁。主要实现了下面3个方法:

  • tryAcquire(int arg):获取独占锁

  • tryRelease(int arg):释放独占锁

  • tryAcquireShared(int arg):获取共享锁

  • tryReleaseShared(int arg):释放共享锁

  • isHeldExclusively:当前线程是否占有独占锁

可见ReentrantReadWriteLock里面同时用到了共享锁和独占锁。

下图是定义的几个常用变量:

State实现锁的原理

下面这2个方法用户获取共享锁和独占锁的数量:

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; } static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

从sharedCount可以看到,共享锁的数量要右移16位获取,也就是说共享锁占了高16位。从上图EXCLUSIVE_MASK的定义看到,跟EXCLUSIVE_MASK进行与运算,得到的是低16位的值,所以独占锁占了低16位。如下图:

State实现锁的原理

这样上面获取锁数量的方法就很好理解了。参考1[1]

2.1 读锁

读锁的实现对应内部类ReadLock。

2.1.1 获取读锁

获取读锁实际上是ReadLock调用了AQS的下面方法,传入参数是1:

public final void acquireShared(int arg) {     if (tryAcquireShared(arg) < 0)         doAcquireShared(arg); }

ReentrantReadWriteLock内部类Sync实现了tryAcquireShared方法,主要包括如下三种情况:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 使用exclusiveCount方法查看state中是否有独占锁,如果有并且独占线程不是当前线程,返回-1,获取失败。

  3. 使用sharedCount查看state中共享锁数量,如果读锁数量小于最大值(MAX_COUNT=65535),则再满足下面3个条件就可以获取成功并返回1:

  • 当前线程不需要阻塞(readerShouldBlock)。在公平锁中,需要判断是否有前置节点,如下图就需要阻塞:

State实现锁的原理

在非公平锁中,则是判断第一个节点是不是有独占锁,如下图就需要阻塞:

State实现锁的原理

  • 使用CAS把state的值加SHARED_UNIT(65536)。

这里是不是就更理解读锁占高位的说法了,获取一个读锁,state的值就要加SHARED_UNIT这么多个。

  • 给当前线程的holdCount加1。

如果2失败,自旋,重复上面的步骤直到获取到锁。

tryAcquireShared(获取共享锁)会返回一个整数,如下:

  • 返回负数:获取锁失败。

  • 返回0:获取锁成功但是之后再由线程来获取共享锁时就会失败。

  • 返回正数:获取锁成功而且之后再有线程来获取共享锁时也可能会成功。

2.1.2 释放读锁

ReentrantReadWriteLock释放读锁是在ReadLock中调用了AQS下面方法,传入的参数是1:

public final boolean releaseShared(int arg) {     if (tryReleaseShared(arg)) {         doReleaseShared();         return true;     }     return false; }

ReentrantReadWriteLock内部类Sync实现了releaseShared方法,具体逻辑分为下面两步:

当前线程holdCounter值减1。

CAS的方式将state的值减去SHARED_UNIT。

2.2 写锁

写锁的实现对应内部类WriteLock。

2.2.1 获取写锁

ReentrantReadWriteLock获取写锁其实是在WriteLock中调用了AQS的下面方法,传入参数1:

public final void acquire(int arg) {     if (!tryAcquire(arg) &&         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))         selfInterrupt(); }

在ReentrantReadWriteLock内部类Sync实现了tryAcquire方法,首先获取state值和独占锁数量(exclusiveCount),之后分如下两种情况,如下图:

State实现锁的原理

state不等于0:

  • 独占锁数量等于0,这时说明有线程占用了共享锁,如果当前线程不是独占线程,获取锁失败。

  • 独占锁数量不等于0,独占锁数量加1后大于MAX_COUNT,获取锁失败。

  • 上面2种情况不符合,获取锁成功,state值加1。

state等于0,判断当前线程是否需要阻塞(writerShouldBlock)。

在公平锁中,跟readerShouldBlock的逻辑完全一样,就是判断队列中head节点的后继节点是不是当前线程。在非公平锁中,直接返回false,即可以直接尝试获取锁。

如果当前线程不需要阻塞,并且给state赋值成功,使用CAS方式把state值加1,把独占线程置为当前线程。

2.2.2 释放写锁

ReentrantReadWriteLock释放写锁其实是在WriteLock中调用了AQS的下面方法,传入参数1:

public final boolean release(int arg) {     if (tryRelease(arg)) {         Node h = head;         if (h != null && h.waitStatus != 0)             unparkSuccessor(h);         return true;     }     return false; }

ReentrantReadWriteLock在Sync中实现了tryRelease(arg)方法,逻辑如下:

  • 判断当前线程是不是独占线程,如果不是,抛出异常。

  • state值减1后,用新state值判断独占锁数量是否等于0

  • 如果等于0,则把独占线程置为空,返回true,这样上面的代码就可以唤醒队列中的后置节点了

  • 如果不等于0,返回false,不唤醒后继节点。

3 CountDownLatch

我们先来看一下UML类图:

State实现锁的原理

从上面的图中看出,CountDownLatch的内部类Sync实现了获取共享锁和释放共享锁的逻辑。

使用CountDownLatch时,构造函数会传入一个int类型的参数count,表示调动count次的countDown后主线程才可以被唤醒。

public CountDownLatch(int count) {     if (count < 0) throw new IllegalArgumentException("count < 0");     this.sync = new Sync(count); }

上面的Sync(count)就是将AQS中的state赋值为count。

3.1 await

CountDownLatch的await方法调用了AQS中的acquireSharedInterruptibly(int  arg),传入参数1,不过这个参数并没有用。代码如下:

public final void acquireSharedInterruptibly(int arg)         throws InterruptedException {     if (Thread.interrupted())         throw new InterruptedException();     if (tryAcquireShared(arg) < 0)         doAcquireSharedInterruptibly(arg); }

Sync中实现了tryAcquireShared方法,await逻辑如下图:

State实现锁的原理

上面的自旋过程就是等待state的值不断减小,只有state值成为0的时候,主线程才会跳出自旋执行之后的逻辑。

3.2 countDown

CountDownLatch的countDown方法调用了AQS的releaseShared(int  arg),传入参数1,不过这个参数并没有用。内部类Sync实现了tryReleaseShared方法,逻辑如下图:

State实现锁的原理

3.3 总结

CountDownLatch的构造函数入参值会赋值给state变量,入队操作是主线程入队,每个子线程调用了countDown后state值减1,当state值成为0后唤醒主线程。

4 Semaphore

Semaphore是一个信号量,用来保护共享资源。如果线程要访问共享资源,首先从Semaphore获取锁(信号量),如果信号量的计数器等于0,则当前线程进入AQS队列阻塞等待。否则,线程获取锁成功,信号量减1。使用完共享资源后,释放锁(信号量加1)。

Semaphore跟管程模型不一样的是,允许多个(构造函数的permits)线程进入管程内部,因此也常用它来做限流。

UML类图如下:

State实现锁的原理

Semaphore的构造函数会传入一个int类型参数,用来初始化state的值。

4.1 acquire

获取锁的操作调用了AQS中的acquireSharedInterruptibly方法,传入参数1,代码见CountDownLatch中await小节。Semaphore在公平锁和非公平锁中分别实现了tryAcquireShared方法。

4.1.1 公平锁

Semaphore默认使用非公平锁,如果使用公平锁,需要在构造函数指定。获取公平锁逻辑比较简单,如下图:

State实现锁的原理

4.1.2 非公平锁

acquire在非公平的锁唯一的区别就是不会判断AQS队列是否有前置节点(hasQueuedPredecessors),而是直接尝试获取锁。

除了acquire方法外,还有其他几个获取锁的方法,原理类似,只是调用了AQS中的不同方法。

4.2 release

释放锁的操作调用了AQS中的releaseShared(int  arg)方法,传入参数1,在内部类Sync中实现了tryReleaseShared方法,逻辑很简单:使用CAS的方式将state的值加1,之后唤醒队列中的后继节点。

5 ThreadPoolExecutor

ThreadPoolExecutor中也用到了AQS,看下面的UML类图:

State实现锁的原理

Worker主要在ThreadPoolExecutor中断线程的时候使用。Worker自己实现了独占锁,在中断线程时首先进行加锁,中断操作后释放锁。按照官方说法,这里不直接使用ReentrantLock的原因是防止调用控制线程池的方法(类似setCorePoolSize)时能够重新获取到锁,

5.1 tryAcquire

使用CAS的方式把AQS中state从0改为1,把当前线程置为独占线程。

5.2 tryRelease

把独占线程置为空,把AQS中state改为0。

Worker初始化的时候会把state置为-1,这样是不能获取锁成功的。只有调用了runWorker方法,才会通过释放锁操作把state更为0。这样保证了只中断运行中的线程,而不会中断等待中的线程。

到此,关于“State实现锁的原理”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程笔记网站,小编会继续努力为大家带来更多实用的文章!


推荐阅读
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 广度优先遍历(BFS)算法的概述、代码实现和应用
    本文介绍了广度优先遍历(BFS)算法的概述、邻接矩阵和邻接表的代码实现,并讨论了BFS在求解最短路径或最短步数问题上的应用。以LeetCode中的934.最短的桥为例,详细阐述了BFS的具体思路和代码实现。最后,推荐了一些相关的BFS算法题目供大家练习。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 解决.net项目中未注册“microsoft.ACE.oledb.12.0”提供程序的方法
    在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错“未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”。本文提供了解决这个问题的方法,包括错误描述和代码示例。通过注册提供程序和修改连接字符串,可以成功读取excel文件信息。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • PL2303HXD电路图(USB转UART)介绍及应用
    本文介绍了PL2303HXD电路图(USB转UART)的特性和应用,该电路图可以实现RS232和USB信号的转换,方便嵌入到手持设备中。PL2303HXD作为USB/RS232双向转换器,可以将USB数据转换为RS232信息流格式发送给外设,并将RS232外设的数据转换为USB数据格式传送回主机。通过利用USB块传输模式和自动流量控制,PL2303HXD能够实现更高的数据传输吞吐量比传统的UART端口。 ... [详细]
author-avatar
mobiledu2502859427
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有