春节越来越近了,疫情也越来越严重,但挡不住叫练携一家老小回老家(湖北)团聚的冲动。响应国家要求去我们做核酸检测了。

独占锁


早上叫练带着一家三口来到了南京市第一医院做核酸检测,护士小姐姐站在医院门口拦着告诉我们人比较多,无论大人小孩,需要排队一个个等待医生采集唾液检测,OK,下面我们用代码+图看看我们一家三口是怎么排队的!

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @author :jiaolian
* @date :Created in 2021-01-22 10:33
* @description:独占锁测试
* @modified By:
* 公众号:叫练
*/
public class ExclusiveLockTest { private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); //医院
private static class Hospital { private String name; public Hospital(String name) {
this.name = name;
} //核酸检测排队测试
public void checkUp() {
try {
writeLock.lock();
System.out.println(Thread.currentThread().getName()+"正在做核酸检测");
//核酸过程...难受...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
} public static void main(String[] args) throws InterruptedException {
Hospital hospital = new Hospital("南京市第一医院");
Thread JLWife = new Thread(()->hospital.checkUp(),"叫练妻");
JLWife.start();
//睡眠100毫秒是让一家三口是有顺序的排队去检测
Thread.sleep(100);
Thread JLSon = new Thread(()->hospital.checkUp(),"叫练子");
JLSon.start();
Thread.sleep(100);
Thread JL = new Thread(()->hospital.checkUp(),"叫练");
JL.start();
}
}

如上代码:在主线程启动三个线程去医院门口排队,女士优先,叫练妻是排在最前面的,中间站的是叫练的孩子,最后就是叫练自己了。我们假设模拟了下核酸检测一次需要3秒。代码中我们用了独占锁,独占锁可以理解成医院只有一个医生,一个医生同时只能为一个人做核酸,所以需要逐个排队检测,所以代码执行完毕一共需要花费9秒,核酸检测就可以全部做完。代码逻辑还是比较简单,和我们之前文章描述synchronized同理。核酸排队我们用图描述下吧!

AQS全称是AbstractQueueSynchroniz,意为队列同步器,本质上是一个双向链表,在AQS里面每个线程都被封装成一个Node节点,每个节点都通过尾插法添加。另外节点还有还封装状态信息,比如是独占的还是共享的,如上面的案例就表示独占Node,医生他本身是一种共享资源,在AQS内部里面叫它state,用int类型表示,线程都会通过CAS的方式争抢state。线程抢到锁了,就自增,没有抢到锁的线程会阻塞等待时机被唤醒。如下图:根据我们理解抽象出来AQS的内部结构。



**根据上面描述,大家看AQS不就是用Node封装线程,然后把线程按照先来后到(非公平锁除外)连接起来的双向链表嘛!关于非公平锁我之前写《排队打饭》案例中也通过简单例子描述过。有兴趣童鞋可以翻看下!

**

**

共享锁


上面我们做核酸的过程是同步执行的,叫独占锁。那共享锁是什么意思呢?现在叫练孩子只有3岁,不能独立完成核酸检测,护士小姐姐感同身受,观察叫练子是排在叫练妻后面的,就让他们一起同时做核酸检测。这种同时做核酸的操作,相当于同时去获取医生资源,我们称之为共享锁。下面是我们测试代码。

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @author :jiaolian
* @date :Created in 2021-01-21 19:54
* @description:共享锁测试
* @modified By:
* 公众号:叫练
*/
public class SharedLockTest { private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); //医院
private static class Hospital { private String name; public Hospital(String name) {
this.name = name;
} //核酸检测排队测试
public void checkUp() {
try {
readLock.lock();
System.out.println(Thread.currentThread().getName()+"正在做核酸检测");
//核酸过程...难受...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
} public static void main(String[] args) throws InterruptedException {
Hospital hospital = new Hospital("南京市第一医院");
Thread JLWife = new Thread(()->hospital.checkUp(),"叫练妻");
JLWife.start();
//睡眠100毫秒是让一家三口是有顺序的排队去检测
Thread.sleep(100);
Thread JLSon = new Thread(()->hospital.checkUp(),"叫练子");
JLSon.start();
/*Thread.sleep(100);
Thread JL = new Thread(()->hospital.checkUp(),"叫练");
JL.start();*/
} }

上面代码我们用ReentrantReadWriteLock.ReadLock作为读锁,在主线程启动“叫练妻”和“叫练”两个线程,本来母子俩一共需要6秒才能完成的事情,现在只需要3秒就可以做完,共享锁好处是效率比较高。如下图,是AQS内部某一时刻Node节点状态。对比上图,Node的状态变为了共享状态,这些节点可以同时去共享医生资源

synchronized锁不响应中断


/**
* @author :jiaolian
* @date :Created in 2020-12-31 18:17
* @description:sync不响应中断
* @modified By:
* 公众号:叫练
*/
public class SynchronizedInterrputedTest { private static class MyService { public synchronized void lockInterrupt() {
try {
System.out.println(Thread.currentThread().getName()+" 获取到了锁");
while (true) {
//System.out.println();
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public static void main(String[] args) throws InterruptedException {
MyService myService = new MyService();
//先启动线程A,让线程A先拥有锁
Thread threadA = new Thread(()->{
myService.lockInterrupt();
});
threadA.start();
Thread.sleep(1000);
//启动线程B,中断,synchronized不响应中断!
Thread threadB = new Thread(()->{
myService.lockInterrupt();
});
threadB.start();
Thread.sleep(1000);
threadB.interrupt();
}
}

如上述代码:先启动A线程,让线程A先拥有锁,睡眠1秒再启动线程B是让B线程处于可运行状态,隔1秒后再中断B线程。在控制台输出如下:A线程获取到了锁,等待2秒后控制台并没有立刻输出报错信息,程序一直未结束执行,说明synchronized锁不响应中断,需要B线程获取锁后才会输出线程中断报错信息!

AQS响应中断


经常做比较知识才会融会贯通,在Lock提供lock和lockInterruptibly两种获取锁的方式,其中lock方法和synchronized是不响应中断的,那下面我们看看lockInterruptibly响应中断是什么意思。我们还是用核酸案例说明。

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @author :jiaolian
* @date :Created in 2021-01-22 15:18
* @description:AQS响应中断代码测试
* @modified By:
* 公众号:叫练
*/
public class AQSInterrputedTest { private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); //医院
private static class Hospital { private String name; public Hospital(String name) {
this.name = name;
} //核酸检测排队测试
public void checkUp() {
try {
writeLock.lockInterruptibly();
System.out.println(Thread.currentThread().getName()+"正在做核酸检测");
//核酸过程...难受...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
} public static void main(String[] args) throws InterruptedException {
Hospital hospital = new Hospital("南京市第一医院");
Thread JLWife = new Thread(()->hospital.checkUp(),"叫练妻");
JLWife.start();
//睡眠100毫秒是让一家三口是有顺序的排队去检测
Thread.sleep(100);
Thread JLSon = new Thread(()->hospital.checkUp(),"叫练子");
JLSon.start();
Thread.sleep(100);
Thread JL = new Thread(()->hospital.checkUp(),"叫练");
JL.start();
//等待1秒,中断叫练线程
System.out.println("护士小姐姐想和叫练私聊会!");
Thread.sleep(1000);
JL.interrupt();
}
}

如上代码:叫练一家三口采用的是独占锁排队去做核酸,叫练线程等待一秒后,护士小姐姐想和叫练私聊会!莫非小姐姐会有啥想法,于是叫练立刻中断了这次的核酸检测,注意是立刻中断。控制台打印结果如下:叫练妻线程和叫练子线程都做了核酸,但叫练却没有做成功!因为被护士小姐姐中断了,结果如下图所示。所以我们能得出结论,在aqs中锁是可以响应中断的。现在如果将上述代码中lockInterruptibly方法换成lock方法会发生什么情况呢,如果换成这种方式,小姐姐再来撩我,叫练要先成功获取锁,也就说叫练已经到医生旁边准备做核酸了,小姐姐突然说有事找叫练,最终导致叫练没有做核酸,碰上这样的事,只能说小姐姐是存心的,小姐姐太坏了。关于lock方法不响应中断的测试大家可以自己测试下。看看我是不是冤枉护士小姐姐了。

我们可以得出结论:在aqs中如果一个线程正在获取锁或者处于等待状态,另一个线程中断了该线程,响应中断的意思是该线程立刻中断,而不响应中断的意思是该线程需要获取锁后再中断。



条件队列


人生或许有那么些不如意。漫长的一个小时排队等待终于过去了,轮到我们准备做核酸了,你说气不气,每次叫练妻出门都带身份证,可偏偏回家这次忘记了?我们用代码看看叫练一家三口在做核酸的过程中到底发生了啥事情?又是怎么处理的!

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantReadWriteLock; /**
* @author :jiaolian
* @date :Created in 2021-01-22 16:10
* @description:条件队列测试
* @modified By:
* 公众号:叫练
*/
public class ConditionTest { private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); //条件队列
private static Condition condition = writeLock.newCondition(); //医院
private static class Hospital { private String name; public Hospital(String name) {
this.name = name;
} //核酸检测排队测试
public void checkUp(boolean isIdCard) {
try {
writeLock.lock();
validateIdCard(isIdCard);
System.out.println(Thread.currentThread().getName()+"正在做核酸检测");
//核酸过程...难受...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName()+"核酸检测完成");
}
} //校验身份信息;
private void validateIdCard(boolean isIdCard) {
//如果没有身份信息,需要等待
if (!isIdCard) {
try {
System.out.println(Thread.currentThread().getName()+"忘记带身份证了");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} //通知所有等待的人
public void singleAll() {
try {
writeLock.lock();
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
} } public static void main(String[] args) throws InterruptedException {
Hospital hospital = new Hospital("南京市第一医院");
Thread.currentThread().setName("护士小姐姐线程");
Thread JLWife = new Thread(()->{
hospital.checkUp(false);
},"叫练妻");
JLWife.start();
//睡眠100毫秒是让一家三口是有顺序的排队去检测
Thread.sleep(100);
Thread JLSon = new Thread(()->hospital.checkUp(true),"叫练子");
JLSon.start();
Thread.sleep(100);
Thread JL = new Thread(()->{
hospital.checkUp(true);
},"叫练");
JL.start();
//等待叫练线程执行完毕
JL.join();
hospital.singleAll();
} }

如上代码:一家人获取独占锁需要排队检测,叫练妻先进去准备核酸,护士小姐姐说先要刷身份证才能进去,叫练妻突然回想起来,出门走得急身份证忘记带了,这可咋办,需要重新排队吗?叫练妻很恐慌,护士小姐姐说,要不这样吧,你先赶紧回家拿,等叫练子,叫练先检测完,我就赶紧安排你进去在做核酸,那样你就不需要重新排队了,这就是上述这段代码的表达意思。我们看看执行结果如下图,和我们分析的结果一致,下图最后画红圈的地方叫练妻最后完成核酸检测。下面我们看看AQS内部经历的过程。

如下图,当叫练妻先获取锁,发现身份证忘带调用await方法会释放持有的锁,并把自己当做node节点放入条件队列的尾部,此时条件队列为空,所以条件队列中只有叫练妻一个线程在里面,接着护士小姐姐会将核酸医生这个资源释放分配给下一个等待者,也就是叫练子线程,同理,叫练子执行完毕释放锁之后会唤醒叫练线程,底层是用LockSupport.unpark来完成唤醒的的操作,相当于基础系列里的wait/notify/notifyAll等方法。当叫练线程执行完毕,后面没有线程了,护士小姐姐调用singleAll方法会见条件队列的叫练妻线程唤醒,并加入到AQS的尾部,等待执行。其中条件队列是一个单向链表,一个AQS可以通过newCondition()对应多个条件队列。这里我们就不单独用代码做测试了。

总结


今天我们用代码+图片+故事的方式说明了AQS重要的几个概念,整理出来希望能对你有帮助,写的比不全,同时还有许多需要修正的地方,希望亲们加以指正和点评,年前这段时间会继续输出实现AQS高级锁,如:ReentrantLock,线程池这些概念等。最后喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。

注意:本故事是自己虚构出来的,仅供大家参考理解。希望大家过年都能顺利回家团聚!

核酸检测:让我明白AQS原理的更多相关文章

  1. AQS 原理以及 AQS 同步组件总结

    1 AQS 简单介绍 AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面. AQS 是一个用来构建锁和同步 ...

  2. AQS原理以及AQS同步组件总结

    1 AQS简单介绍 2 AQS原理

  3. 并发之AQS原理(三) 如何保证并发

    并发之AQS原理(三) 如何保证并发 1. 如何保证并发 AbstractQueuedSynchronizer 维护了一个state(代表了共享资源)和一个FIFO线程等待队列(多线程竞争资源被阻塞时 ...

  4. 并发之AQS原理(一) 原理介绍简单使用

    并发之AQS原理(一) 如果说每一个同步的工具各有各的强大,那么这个强大背后是一个相同的动力,它就是AQS. AQS是什么 AQS是指java.util.concurrent.locks包里的Abst ...

  5. 简述AQS原理

    这是一道面试题:简述AQS原理 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态.如果被请求的共享资源被占用,那么就需要一套线程阻塞 ...

  6. 看完您如果还不明白 Kerberos 原理,算我输!

    系统环境 操作系统:CentOS 6 或 CentOS 7 JDK 版本:1.8.0_151 Ambari 版本:2.6.1 HDP 版本:2.6.4.0 扩展链接 Kerberos原理--经典对话 ...

  7. AQS原理

    1. AQS原理 1.1. 是什么 AQS全程AbstractQueuedSynchronizer抽象队列同步器,它是并发包中的基础类 ReetrantLock,ReentrantReadWriteL ...

  8. 并发之AQS原理(二) CLH队列与Node解析

    并发之AQS原理(二) CLH队列与Node解析 1.CLH队列与Node节点 就像通常医院看病排队一样,医生一次能看的病人数量有限,那么超出医生看病速度之外的病人就要排队. 一条队列是队列中每一个人 ...

  9. 浅谈AQS原理

    一.AQS介绍 AQS,即AbstractQueuedSynchronizer, 抽象队列同步器,它是Java多线程模块用来构建锁和其他同步组件的基础框架.来看下同步组件对AQS的使用: AQS是一个 ...

  10. Java并发——结合CountDownLatch源码、Semaphore源码及ReentrantLock源码来看AQS原理

    前言: 如果说J.U.C包下的核心是什么?那我想答案只有一个就是AQS.那么AQS是什么呢?接下来让我们一起揭开AQS的神秘面纱 AQS是什么? AQS是AbstractQueuedSynchroni ...

随机推荐

  1. REDHAT一总复习1 vim编辑器的使用 删除所有者列 删除指定行

    将文件/home/student/vimfile.txt 复制到server 上的/home/student/longlisting.txt . 根据下列要求,使用vim编辑器更改 /home/stu ...

  2. 防止sql注入,过滤敏感关键字

    //sql过滤关键字 public static bool CheckKeyWord(string sWord) { //过滤关键字 string StrKeyWord = @"select ...

  3. console数据

  4. 【.net 深呼吸】通过标准输入/输出流来完成进程间通信

    实现进程之间煲电话粥的方式,有好几种,比如,你可以用这些方案: 1.使用socket来传递.这个好像很无聊,本地进程之间也用socket?不过,通过本机回环网络确实可以进程之间通信. 2.WCF,与上 ...

  5. 操作系统 - unix和windows下进程异同

    在UNIX系统中,只有一个系统调用可以用来创建新进程:fork.这个系统调用会创建一个与调用进程相同的副本.在调用了fork之后,这两个进程(父进程和子进程)拥有相同的存储映像.同样的环境字符串和同样 ...

  6. Spring Boot 2.x :通过 spring-boot-starter-hbase 集成 HBase

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 本文内容 HBase 简介和应用场景 spring-boot- ...

  7. ExcelDna项目完整工程演示及讲解

    原始链接:http://www.cnblogs.com/Charltsing/p/ExcelDnaDemo.html ExcelDna工程演示讲课内容 1.ExcelDna是啥? 2.ExcelDna ...

  8. Springboot+ActiveMQ(ActiveMQ消息持久化,保证JMS的可靠性,消费者幂等性)

    ActiveMQ 持久化设置: 在redis中提供了两种持久化机制:RDB和AOF 两种持久化方式,避免redis宕机以后,能数据恢复,所以持久化的功能 对高可用程序来说 很重要. 同样在Active ...

  9. ASP.NET Core Identity 实战(4)授权过程

    这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity ...

  10. vue组件库(四):组件功能模块划分

    涉及的平台 移动端 一.公共样式 常用变量 var.scss 颜色模块 主题色.状态色.文本色.灰度色(边框和分隔线) 字体 字体.大小.行间距 2.控件 3. 三大模块 样式.有哪些控件