AQS 原理

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
特点 :

  • 用state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

    • getState - 获取 state 状态
    • setState - 设置 state 状态
    • compareAndSetState - cas 乐观锁机制设置 state 状态
    • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
  • 提供了基于FIFO的等待队列,类似于 Monitor的EntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor的WaitSet

子类主要实现这样一些方法 (默认抛出UnsupportedOperationException)

  • tryAcquire
  • tryRelease
  • tryAcquireShard
  • tryReleaseShard
  • isHeldExclusively
    获取锁的优势

    释放锁的姿势

ReentrantLock 原理

1. 非公平锁实现原理

加锁解锁流程
先从构造器开始看,默认为非公平锁实现

NonfairSync 继承自 AQS
没有竞争时

第一个竞争出现时 :

Thread-1 执行了

  1. CAS尝试将state 由0 改为 1,结果失败
  2. 进入 tryAcquire 逻辑,这时state 已经是1,结果任然失败
  3. 接下来进入 addWaiter逻辑,构造Node队列
    • 图中黄色三角表示该Node的waitStatus状态,其中0为默认正常状态
    • Node的创建时懒得的
    • 其中第一个 Node称为 Dummy (哑元)或哨兵,用来占位,并不关联线程

      当前线程进入 acquireQueued 逻辑
    1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
    2. 如果自己是紧邻着 head (排第二位),那么再次 tryAcquire 尝试获取锁,当然时 state 仍为1,失败
    3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的waitStatus改为 -1,这次返回false
      再次多个线程经历上述过程竞争失败,变成这个样子

      Thread-0 释放锁,进入tryRelease 流程,如果成功
  • 设置exclusiveOwnerThread为null
  • state = 0

    当队列不为null,并且head的waitStatus = -1,进入unparkSuccessor 流程找到队列中离head最近的一个Node(没取消的),unpark恢复其运行,本例中即为Thread-1
    回到 Thread-1 的acquireQueued 流程

    回到 Thread - 1的 acquireQueued 流程

    如果加锁成功(没有竞争),会设置
  • exclusiveOwnerThread 为 Thread - 1,state = 1
  • head 指向刚刚 Thread - 1 所在的Node,该Node清空Thread
  • 原本的head因为从链表断开,而可被垃圾回收
    如果这时候有其它线程来竞争(非公平的体现),例如这时有Thread - 4来了

    如果不巧又被 Thread - 4 占了先
  • Thread - 4被设置为 exclusiveOwnerThread,state = 1
  • Thread - 1再次进入 acquireQueued 流程,获取锁失败,重新进入 park阻塞
    加锁源码

2)可重入原理


3. 可打断原理

不可打断模式
在此模式下,即使它被打断,仍会驻留在AQS队列中,等获得锁后方能继续运行(是继续运行!只是打断标记被设置为true)



可打断模式

5) 条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
await 流程
开始 Thread - 0 持有锁,调用await,进入ConditionObject 的addConditionWaiter流程创建新的Node状态为 -2 (Node.CONDITION),关联Thread - 0,加入等待队列尾部

接下来进入AQS的fullyRelease流程,释放同步器上的锁

unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么Thread - 1竞争成功

park 阻塞 Thread - 0

signal
假设Thread - 1 要来唤醒 Thread - 0

进入 ConditionObject 的doSignal流程,取得等待队列中第一个 Node,即Thread - 0所在Node

执行transferForSignal 流程,将该Node 加入AQS队列尾部,将Thread - 0
的waitStatus改为0,Thread - 3的waitStatus改为 - 1

Thread - 1 释放锁,进入unlock流程。

3. 读写锁

3.1 ReentrantReadWriteLock

当读操作远远高于写操作时,这时候使用读写锁让 读-读可以并发,提高性能。
类似于数据库中的select 。。。from 。。。lock in share mode
提供一个数据容器类内部分别使用读锁保护数据的read()方法,写锁保护数据的write()方法
注意事项

  • 读锁不支持条件变量
  • 重入时升级不支持 :即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
  • 重入时降级支持 :即持有写锁的情况下去获取读锁

缓存更新策略

更新时,是先清缓存还是先更新数据库
先清缓存

先更新数据库

读写锁原理

  1. 图解流程
    读写锁同的是同一个Sycn同步器,因此等待队列、state等也是同一个
    t1 w.lock,t2 r.lock
    1. t1成功上锁,流程与ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了state的低16位,而读锁使用的是state的高16位

      2)t2 执行 t.lock,这时进入读锁的 sync.acquireShared(1)流程,首先会进入tryAcquireShard流程。如果有写锁占据,那么tryAcquireShared返回-1 表示失败
      tryAcquireShared 返回值表示

      • -1 表示失败
      • 0 表示成功,但后继节点继续唤醒
      • 正数表示成功,而且数值是还有几个后继节点需要唤醒,读写锁返回1

        3)这时会进入 sync.doAcquireShared(1)流程,首先也是调用 addWaiter添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.
        EXCLUSIVE模式,注意此时t2仍处于活跃状态

        4)t2会看看自己的节点是不是老二,如果是,还会再次调用tryAcquireShared(1)来尝试获取锁
        5)如果没有成功,在doAcquireShared 内 for (;;)循环一次,把前驱节点的waitStatus改为 -1,再for(;;)循环一次尝试tryAcquireShared(1)如果还不成功,那么在parkAndCheckInterrupt()处park

        t3 r.lock, t4 w.lock
        这种状态下,假设又有t3 加读锁和t4加写锁,这期间t1任然持有锁,就编程了下面样子

        t1 w.unlock
        这时会走到写锁的 sync.release(1)流程,调用sync.tryRelease(1)成功,变成下面的样子

        接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行,这时 t2 在doAcquireShared 内 parkAndCheckInterrupt()处恢复运行
        这回再来一次 for(;;)执行 tryAcquireShared成功则让读锁计数加一

        这时t2 已经恢复运行,接下来t2调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

        事情还没完,在setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是则调用 doReleaseShared 将head的状态从 -1 改为 0并唤醒老二,这时t3在 doAcquireShared 内 parkAncCheckInterrupt() 会恢复运行

        这回再来一次for(;;)执行tryAcquireShared 成功则让读锁计数加一

        这时t3 已经恢复运行,接下来t3 调用 setHeadAndPropagate(node,1),它原本所在节点被置为头节点

        下一个节点不是shared了,因此不会继续唤醒t4所在节点
        t2 r.unlock, t3 r.unlock
        t2 进入 sync.releaseShared(1)中,调用 tryReleaseShared(1)让计数减一,但由于计数为零

        t3 进入 sync.releaseShared(1)中,调用tryReleaseShared(1)让计数减一,这回计数为零了,进入doReleaseShared()将头节点从-1改为0并唤醒老二,即

        之后 t4 在acquireQueued 中 parkAndCheckInterrupt 处恢复运行,再次for(;;)这次自己是老二,并且没有其他竞争,tryAcquire(1)成功,修改头结点,流程结束

并发编程之J.U.C的第一篇的更多相关文章

  1. 并发编程之J.U.C的第二篇

    并发编程之J.U.C的第二篇 3.2 StampedLock 4. Semaphore Semaphore原理 5. CountdownLatch 6. CyclicBarrier 7.线程安全集合类 ...

  2. Java并发编程之CAS第一篇-什么是CAS

    Java并发编程之CAS第一篇-什么是CAS 通过前面几篇的学习,我们对并发编程两个高频知识点了解了其中的一个—volatitl.从这一篇文章开始,我们将要学习另一个知识点—CAS.本篇是<凯哥 ...

  3. [转载]并发编程之Operation Queue和GCD

    并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...

  4. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  5. 并发编程之 Condition 源码分析

    前言 Condition 是 Lock 的伴侣,至于如何使用,我们之前也写了一些文章来说,例如 使用 ReentrantLock 和 Condition 实现一个阻塞队列,并发编程之 Java 三把锁 ...

  6. 并发编程之ThreadLocal

    并发编程之ThreadLocal 前言 当多线程访问共享可变数据时,涉及到线程间同步的问题,并不是所有时候,都要用到共享数据,所以就需要线程封闭出场了. 数据都被封闭在各自的线程之中,就不需要同步,这 ...

  7. 并发编程之:JMM

    并发编程之:JMM 大家好,我是小黑,一个在互联网苟且偷生的农民工. 上一期给大家分享了关于Java中线程相关的一些基础知识.在关于线程终止的例子中,第一个方法讲到要想终止一个线程,可以使用标志位的方 ...

  8. 并发编程之:ThreadLocal

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 从前上一期[并发编程之:synchronized] 我们学到要保证在并发情况下对于共享资源的安全访问,就需要用到锁. 但是,加锁通常情况下会让运行效率 ...

  9. 并发编程之:Atomic

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 在开始讲今天的内容之前,先问一个问题,使用int类型做加减操作是不是线程安全的呢?比如 i++ ,++i,i=i+1这样的操作在并发情况下是否会有问题 ...

随机推荐

  1. 第三次作业:使用Packet Tracer分析TCP连接的建立与释放过程

    0 个人信息 张樱姿 201821121038 计算1812 1 实验目的 使用路由器连接不同的网络 使用命令行操作路由器 通过抓取HTTP报文,分析TCP连接建立的过程 2 实验内容 使用Packe ...

  2. .windows模拟linux命令iostat的显示

    脚本如下: #!/usr/bin/env python #coding:utf- import win32com.client import time def disk_status(): try: ...

  3. 学习Qt的资源-网站、论坛、博客等

    来自<零基础学Qt 4编程>一书的附录 附录C Qt资源 C.1 Qt 官方资源 全球各大公司以及独立开发人员每天都在加入 Qt 的开发社区.他们已经认识到了Qt 的架构本身便可加快应用程 ...

  4. Bash 脚本中的 set -euxo pipefail

    有些开发人员会用Bash来实现很复杂的功能,就像使用别的高级语言一样.他可能觉得自己很牛逼但其他人早就想锤爆他了,Bash的可读性和可维护性远远低于任何高级语言.更要命的是,Bash并没有方便的调试工 ...

  5. 一个新实验:使用gRPC-Web从浏览器调用.NET gRPC服务

    今天给大家翻译一篇由ASP.NET首席开发工程师James Newton-King前几天发表的一篇博客,文中带来了一个实验性的产品gRPC-Web.大家可以点击文末的讨论帖进行相关反馈.我会在文章末尾 ...

  6. 13.深度学习(词嵌入)与自然语言处理--HanLP实现

    笔记转载于GitHub项目:https://github.com/NLP-LOVE/Introduction-NLP 13. 深度学习与自然语言处理 13.1 传统方法的局限 前面已经讲过了隐马尔可夫 ...

  7. Burpsuite--安装和环境配置

    1.引子 Burpsuite是一款安全人员常用的工具.在渗透测试中,我们使用Burp Suite将使得测试工作变得更加容易和方便,即使在不需要娴熟的技巧的情况下,只有我们熟悉Burp Suite的使用 ...

  8. 《自拍教程9》Python编程风格规范

    Python编程风格规范 根据Python官方提供的Python编程风格规范: Style Guide for Python Code, 即PEP8规范, https://www.python.org ...

  9. kubernetes(14):k8s基于NFS部署storageclass实现pv自动供给

    k8s基于NFS部署storageclass实现pv自动供给 https://www.cnblogs.com/Smbands/p/11059843.html https://www.jianshu.c ...

  10. 小白学 Python 数据分析(7):Pandas (六)数据导入

    人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...