春节愈来愈近了,疫情也愈来愈严重,但挡不住叫练携一家老少回老家(湖北)团聚的冲动。响应国家要求去咱们作核酸检测了。
java
早上叫练带着一家三口来到了南京市第一医院作核酸检测,护士小姐姐站在医院门口拦着告诉咱们人比较多,不管大人小孩,须要排队一个个等待医生采集唾液检测,OK,下面咱们用代码+图看看咱们一家三口是怎么排队的!node
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封装线程,而后把线程按照先来后到(非公平锁除外)链接起来的双向链表嘛!关于非公平锁我以前写《排队打饭》案例中也经过简单例子描述过。有兴趣童鞋能够翻看下!
**
**this
上面咱们作核酸的过程是同步执行的,叫独占锁。那共享锁是什么意思呢?如今叫练孩子只有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的状态变为了共享状态,这些节点能够同时去共享医生资源!
code
/** * @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线程获取锁后才会输出线程中断报错信息!
blog
常常作比较知识才会融会贯通,在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中若是一个线程正在获取锁或者处于等待状态,另外一个线程中断了该线程,响应中断的意思是该线程马上中断,而不响应中断的意思是该线程须要获取锁后再中断。
图片
人生或许有那么些不如意。漫长的一个小时排队等待终于过去了,轮到咱们准备作核酸了,你说气不气,每次叫练妻出门都带身份证,可恰恰回家此次忘记了?咱们用代码看看叫练一家三口在作核酸的过程当中到底发生了啥事情?又是怎么处理的!ip
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,线程池这些概念等。最后喜欢的请点赞加关注哦。我是叫练【公众号】,边叫边练。
注意:本故事是本身虚构出来的,仅供你们参考理解。但愿你们过年都能顺利回家团聚!