java并发框架aqs,深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念

深入理解Java并发框架AQS系列(一):线程

深入理解Java并发框架AQS系列(二):AQS框架简介及锁概念

一、AQS框架简介

AQS诞生于Jdk1.5,在当时低效且功能单一的synchronized的年代,某种意义上讲,她拯救了Java

注:本系列文章所有测试用例均基于jdk1.8,操作系统为macOS

1.1、思考

我们去学习一个知识点或开启一个新课题时,最好是带着问题去学习,这样针对性比较强,且印象比较深刻,主动思考带给我们带来了无穷的好处

抛开AQS,设想以下问题:

Q:如果我们遇到 thread 无法获取所需资源时,该如何操作?

A:不断重试呗,一旦资源释放可快速尝试获取

Q:那如果资源持有时长较长,不断循环获取,是否比较浪费CPU ?

A:的确,那就让线程休息1秒钟,再尝试获取,这样就不会导致CPU空转了

Q:那如果资源在第0.1秒时被释放,那线程岂不是要白白等待0.9秒了 ?

A:实在不行就让当前线程挂起,等释放资源的线程去通知当前线程,这样就不存在等待时间长短的问题了

Q:但如果资源持有时间很短,每次都挂起、唤醒线程成为了一个很大的开销

A:那就依情况而定,lock时间短的,就不断循环重试,时间长的就挂起

Q:如何界定lock的时间长短?还有就是如果lock的时间不固定,也无法预期呢?

A:唔。。。这是个问题

Q:如果线程等待期间,我想放弃呢?

A:。。。。。。

Q:还有很多问题

如果我想动态增加资源呢?

如何我不想产生饥饿,而保证加锁的有序性呢?

或者我要支持/不支持可重入特性呢?

我要查看所有等待资源的线程状态呢?

。。。。。。

我们发现,一个简单的等待资源的问题,牵扯出后续诸多庞杂且无头绪的问题;加锁不仅依赖一套完善的框架体系,还要具体根据使用场景而定,才能接近最优解;那我们即将要引出的AQS能完美解决上述这些问题吗?

答案是肯定的:不能

其实Doug Lea也意识到问题的复杂性,不可能出一个超级工具来解决所有问题,所以他把AQS设计为一个abstract类,并提供一系列子类去解决不同场景的问题,例如ReentrantLock、Semaphore等;当我们发现这些子类也不能满足我们加锁需求时,我们可以定义自己的子类,通过重写两三个方法,寥寥几行代码,实现强大的功能,这一切都得益于AQS作者敏锐的前瞻性

指的一提的是,虽然我们可以用某个子类去实现另一个子类所提供的功能(例如使用Semaphore替代CountDownLatch),但其易用、简洁、高效性等能否达到理想效果,都值得商榷;就好比在陆地上穿着雪橇走路,虽能前进,却低效易摔跤

1.2、并发框架

本小节仅带大家对AQS架构有个初步了解,在后文的独占锁、共享锁等中会详细阐述。下图为AQS框架的主体结构

e3a095a76a7943865908f855a94b0407.png

从上图中我们看到了AQS中非常关键的一个概念:“阻塞队列”。即AQS的理念是当线程无法获取资源时,提供一个FIFO类型的有序队列,用来维护所有处于“等待中”的线程。看似无解可击的框架设计,同时也牵出另外的一个问题:阻塞队列一定高效吗?

当“同步块逻辑”执行很快时,我们列出两种场景

场景1:直接使用AQS框架,例如试用其子类ReentrantLock,遇到资源争抢,放阻塞队列

场景2:因为锁占用时间短,无限重试

针对这2种场景,我们写测试用例比较一下

package org.xijiu.share.aqs.compare;import org.junit.Test;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.AbstractQueuedSynchronizer;import java.util.concurrent.locks.ReentrantLock;/** * @author likangning * @since 2021/3/9 上午8:58 */public class CompareTest { private class MyReentrantLock extends AbstractQueuedSynchronizer { protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); while (true) { int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } } } protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } } /** * 使用AQS框架 */ @Test public void test1() throws InterruptedException { ReentrantLock reentrantLock = new ReentrantLock(); long begin = System.currentTimeMillis(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 2; i++) { executorService.submit(() -> { for (int j = 0; j < 50000000; j++) { reentrantLock.lock(); doBusiness(); reentrantLock.unlock(); } }); } executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); System.out.println("ReentrantLock cost : " + (System.currentTimeMillis() - begin)); } /** * 无限重试 */ @Test public void test2() throws InterruptedException { MyReentrantLock myReentrantLock = new MyReentrantLock(); long begin = System.currentTimeMillis(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 2; i++) { executorService.submit(() -> { for (int j = 0; j < 50000000; j++) { myReentrantLock.tryAcquire(1); doBusiness(); myReentrantLock.tryRelease(1); } }); } executorService.shutdown(); executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); System.out.println("MyReentrantLock cost : " + (System.currentTimeMillis() - begin)); } private void doBusiness() { // 空实现,模拟程序快速运行 }}

上例,虽然MyReentrantLock继承了AbstractQueuedSynchronizer,但没有使用其阻塞队列。我们每种情况跑5次,看下两者在耗时层面的表现

耗时1

耗时2

耗时3

耗时4

耗时5

平均耗时(ms)

ReentrantLock

11425

12301

12289

10262

11461

11548

MyReentrantLock

8717

8957

10283

8445

8928

9066

上例只是拿独占锁举例,共享锁也同理。可以简单概括为:线程挂起、唤醒的时间占整个加锁周期比重较大,导致每次挂起、唤醒已经成为一种负担。当然此处并不是说AQS设计有什么缺陷,只是想表达并没有一种万能的框架能应对所有情况,一切都要靠使用者灵活理解、应用

1.3、拓扑结构及如何使用

我们常用的锁并发类,基本上都是AQS的子类或通过组合方式实现,可见AQS在Java并发体系的重要性

49d25e469cbca9cd1b20657bc0b9b574.png

至于如何使用,是需要区分子类是想实现独占锁还是共享锁

独占锁

tryAcquire()

tryRelease()

isHeldExclusively() -- 可不实现

共享锁

tryAcquireShared()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值