Java理论六: AQS核心思想和相关锁(面试知识点)

本文深入探讨了Java并发编程中的AQS(AbstractQueuedSynchronizer)同步器,阐述了其作为多线程同步的核心思想,包括独占与共享模式的资源获取与释放。通过AQS可以构建ReentrantLock、Semaphore、CountDownLatch等同步组件,并介绍了自定义同步器的实现方法。此外,文章还讲解了AQS对线程阻塞队列的维护,以及如何使用AQS来实现一个简单的同步机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考致谢:
https://blog.youkuaiyun.com/wanliguodu/article/details/81154294#commentBox
Java并发之AQS详解 - waterystone - 博客园 (cnblogs.com)
https://blog.youkuaiyun.com/vernonzheng/article/details/8275624
JUC AQS ReentrantLock源码分析(一)_java_lyvee的专栏-优快云博客

1 什么是AQS?

1.1 同步器

多线程并发的执行,之间通过某种共享状态来同步,只有当状态满足共享资源空闲/忙碌条件时,才能触发线程执行锁定/等待 。这个共同的语义可以称之为同步器。

Node:构建锁或者其他同步组件的基础框架的关键(锁是面向使用者,同步器面向锁的实现者)
state:共享资源,线程竞争失败,加到CLH队列里。

所有实现同步的锁机制都可以基于同步器定制来实现的。juc(java.util.concurrent) 里所有的这些锁机制都是基于 AQS( AbstractQueuedSynchronizer )框架上构建的。

1.2 AQS核心思想

AQS( AbstractQueuedSynchronizer)抽象队列同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch等。
在这里插入图片描述

AQS定义两种资源共享方式:
Exclusive:独占,只有一个线程能执行,如ReentrantLock。
Share:共享,多个线程可同时执行,如Semaphore/CountDownLatch。

2 AQS构建同步器

2.1 AQS与同步

1、一个同步器至少需要包含两个功能:
1)获取同步状态:如果允许,则获取锁,如果不允许就阻塞线程,直到同步状态允许获取。
2)释放同步状态:修改同步状态,并且唤醒等待线程。

2、AQS获取和释放同步状态
1)独占模式
独占获取:tryAcquire 本身不会阻塞线程,true 就继续,false 就阻塞线程并加入阻塞队列。
独占且可中断模式获取:acquireInterruptibly支持中断取消。
独占且支持超时模式获取: tryAcquireNanos带有超时时间,如果经过超时时间则会退出。
独占模式释放:release释放成功会唤醒后续节点。
2)共享模式
共享模式获取:tryAcquireShared
可中断模式共享获取:acquireSharedInterruptibly
共享模式带定时获取:tryAcquireSharedNanos
共享锁释放:releaseShared

3、状态获取、释放成功或失败的后续行为:线程的阻塞、唤醒机制
LockSupport.park() :线程的禁用(park)
LockSupport.unpark() :线程的唤醒(unpark)

补充:唤醒一个线程主要手段包括以下几种:
1)其他线程调用被park()的线程——unpark(Thread thread),第一时间返回的是当前线程是否被打断。
2)其他线程中断被park()线程——waiters.peek().interrupt(),waiters为存储线程对象的队列。
3)不知原因的返回。

2.2 自定义同步器

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了(模板设计模式)。

自定义同步器实现时主要实现以下几种方法:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式,尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式,尝试释放资源,成功则返回true,失败返回false。
tryAcquireShared(int):共享方式,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式,尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

2.3 AQS实现的不同同步器

基于AQS构建的Synchronizer有ReentrantLock,Semaphore,CountDownLatch, ReetrantRead WriteLock,FutureTask等,这些Synchronizer实际上最基本的东西就是原子状态的获取和释放,只是条件不一样而已。

1、 ReentrantLock
独占式锁,需要记录当前线程获取原子状态的次数,如果次数为零,那么就说明这个线程放弃了锁,如果次数大于1,也就是获得了重进入的效果,而其他线程只能被park住,直到这个线程重进入锁次数变成0而释放原子状态。

2、Semaphore信号量
共享式锁,记录当前还有多少次许可可以使用,到0,就需要等待,也就实现并发量的控制,Semaphore一开始设置许可数为1,实际上就是一把互斥锁。

3、CountDownLatch
闭锁,countDown如果为0,说明可以继续执行,否则需要park住,等待countDown次数足够,并且unpark所有等待线程,释放原子状态,让等待的所有线程通过。

4、FutureTask
需要记录任务的执行状态,当调用其实例的get方法时,只检查当前任务是否完成或者被Cancel,如果未完成并且没有被cancel,那么告诉AQS当前线程需要进入等待队列并且park住。而跑任务的线程会在任务结束时调用FutureTask 实例的set方法(与等待线程持相同的实例),设定执行结果,并且通过unpark唤醒正在等待的线程,返回结果。

3 AQS对线程阻塞队列的维护

AQS里将阻塞线程封装到一个内部类Node里。并维护一个CHL Node FIFO队列。CHL队列是一个非阻塞的FIFO队列,也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和CAS保证节点插入和移除的原子性。实现无锁且快速的插入。

以ReentrantLock为例,state初始化为0,表示未锁定状态。线程1 lock()时,会调用tryAcquire()独占该锁并将state+1。此后其他线程再tryAcquire()时就会失败,直到线程1 unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,线程1自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

4 自己如何实现一个同步?

来自视频+文章:https://blog.youkuaiyun.com/java_lyvee/article/details/98966684
https://www.bilibili.com/video/BV1uV411z7Tu?p=2&t=502

1、自旋CAS—>没有竞争到锁的线程再次竞争
缺点:耗费cpu资源,可能需要等待其他线程释放锁很久。
在这里插入图片描述

2、yield+自旋 -->得不到锁的线程让出CPU
缺点:让出不能保证该线程下次不竞争cpu,让出之后可能还会占用cpu。
在这里插入图片描述

3、sleep+自旋----抢不到锁的线程等待一段时间后,再退出cpu
使用sleep的缺点:sleep的时间不好控制 -->引出locksupport.park()
在这里插入图片描述

4、lockSupport.park()+自旋 :—>接近于ReentranLock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值