【JUC并发编程】AQS底层实现原理(源码解读/ 核心参数/ 公平锁/ 非公平锁/ 锁池/ 等待池)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 【JUC并发编程】AQS底层实现原理(源码解读/ 核心参数/ 公平锁/ 非公平锁/ 锁池/ 等待池),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


1. AQS底层实现设计技术点

  1. CAS 用户态 保证原子性 —cpu硬件层面的支持 java语言调用cas
  2. 双向链表(阻塞队列) 数组实现队列或者基于链表实现队列
  3. LockSupport

在这里插入图片描述

2. AQS基本的概念

aqs全称为AbstractQueuedSynchronizer 是一个抽象同步队列,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLock、CountDownLatch等。
AQS是一个抽象类,主要是通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及释放的方法来提供自定义的同步组件

3. AQS源码解读

Lock锁底层原理 aqsapi封装的
NonfairSync 非公平锁
FairSync 公平锁
ReentrantLock 在默认的情况下就是属于非公平锁

公平锁与非公平锁最大的区别:就是在做cas操作的是时候加上 !hasQueuedPredecessors()
必须遵循公平竞争
在这里插入图片描述
步器的实现是 ABS 核心(state 资源状态计数)
同步器的实现是 ABS 核心,以 ReentrantLock 为例,state 初始化为 0,表示未锁定状态。A 线程
lock()时,会调用 tryAcquire()独占该锁并将 state+1。此后,其他线程再 tryAcquire()时就会失
败,直到 A 线程 unlock()到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放
锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,
获取多少次就要释放多么次,这样才能保证 state 是能回到零态的。
以 CountDownLatch 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与
线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后 countDown()一次,state
会 CAS 减 1。等到所有子线程都执行完后(即 state=0),会 unpark()主调用线程,然后主调用线程
就会从 await()函数返回,继续后余动作。
ReentrantReadWriteLock 实现独占和共享两种方式
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现 tryAcquiretryRelease、tryAcquireShared-tryReleaseShared 中的一种即可。但 AQS 也支持自定义同步器
同时实现独占和共享两种方式,如 ReentrantReadWriteLock。

4. AQS核心参数

AQS类中核心参数

  1. Node结点 采用双向链表的形式存放正在等待的线程 waitStatus状态、thread等到锁的线程
  2. waitStatus状态:
    CANCELLED,值为1,表示当前的线程被取消;
    SIGNAL,值为-1,释放资源后需唤醒后继节点;
    CONDITION,值为-2, 等待condition唤醒;
    PROPAGATE,值为-3,工作于共享锁状态,需要向后传播,比如根据资源是否剩余,唤醒后继节点;
    值为0,表示当前节点在sync队列中,等待着获取锁。
  3. Head 头结点 等待队列的头结点
  4. Tail 尾结点 正在等待的线程
  5. State 锁的状态 0 无锁、1有线程获取到锁, 当前线程重入不断+1
  6. exclusiveOwnerThread 记录锁的持有线程

非公平锁实现:

  1. 使用CAS 修改AQS类中状态值 从0改1 (多个同时执行该代码 最终只会有一个线程改成功)
  2. 如果使用CAS 修改AQS的状态 为1 改成功 则 在AQS中类中 使用该属性exclusiveOwnerThread
    记录我们获取到锁的线程
    3.如果使用CAS修改AQS的状态 失败的(获取锁失败),则走AQS类中方法acquire()
    当前线程就会直接存放在AQS类中 双向链表的尾部.
    头节点是没有存放任何的线程,头节点的next节点开始存放执行CAS失败的线程
    在这里插入图片描述

5. AQS中为什么头结点是为空的

头结点可以简单理解就是可以不用参加排队,表示已经获取到锁的线程,已经在aqs类中已经记录绑定获取到锁的线程,所以head结点直接设置线程为null,防止浪费空间内存。

6. 非公平锁实现原理

获取锁:

  1. 使用cas 修改锁的状态0改成1;
  2. 如果使用cas修改成功,则当前aqs exclusiveOwnerThread 记录当前线程获取锁。
  3. 如果当前线程重复获取锁,则当前aqs状态+1 重入锁实现。
  4. 如果使用cas 修改失败,则当前线程使用cas 在aqs双向链表尾部追加当前线程,同时当前线程也会阻塞 使用 LockSupport

释放锁:

  1. 当前aqs状态 -releases 如果 为0 则会释放锁;
  2. 使用cas 修改AQS的状态
  3. 唤醒阻塞队列中头节点下一个节点阻塞线程从新进入到竞争锁状态
  4. 唤醒的线程cas操作成功,则会从双向中移除。

注意:如果在没有其他的线程竞争锁时,则aqs 类中 head 节点是为null

7. 公平锁实现原理

公平锁与非公平锁的实现原理区别:

  1. 非公平锁调用lock方法,首先会执行一次CAS操作、如果CAS成功则当前线程获取到锁
    ,如果CAS失败之后,在重试的过程中如果AQS锁的状态为0,则可以继续执行CAS操作。
  2. 公平锁调用lock方法,如果已经有其他线程获取到该锁,则当前线程直接追加双向链表后面,不会参与CAS操作。

9. Condition

9.1 锁池

锁池: 假设线程A已经拥有了某个对象的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),
由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,
所以这些线程就进入了该对象的锁池中。
EntryList (锁池) 当前的线程获取锁失败,阻塞 链表数据结构存放

9.2 等待池

1.如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
2.当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
3.优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131197.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!