最详细的图文解析Java各种锁(终极篇)
通过本篇文章,你将了解到:
1、锁的全家福
2、如何验证公平/非公平锁
3、底层如何获取锁/释放锁
4、自旋锁与自适应自旋
5、为什么需要等待/通知机制 1、锁的全家福
2、如何验证公平/非公平锁
公平与非公平区别之处在于获取锁时的策略。
如上图:
1、线程1持有锁。
2、线程2、线程3、线程4 在同步队列里排队等候锁。
这时线程5也想要获取锁,根据公平与否分为两种不同策略。
公平锁
线程5先判断同步队列是是否有线程在等待,明显地此时同步队列里有线程在等待,于是线程5加入到同步队列的尾部等待。
非公平锁
1、线程5不管同步队列是否有线程等待,管他三七二十一先去抢锁再说。若是运气好就能直接捡到便宜获取了锁,若是失败再去排队。
2、线程5还是有机会捡便宜的,若是此时线程1刚好释放了锁,并唤醒线程2,线程2醒过来后去获取锁。若在线程2获取锁之前线程5就去抢锁了,那么它会成功。它的成功对于线程2、线程3、线程4来说是不公平的。
我们知道ReentrantLock 可实现公平/非公平锁,来验证一下。
先来验证公平锁: public class TestThread { private ReentrantLock reentrantLock = new ReentrantLock(true); private void testLock() { for (int i = 0; i < 5; i++) { Thread thread = new Thread(runnable); thread.setName("线程" + (i + 1)); thread.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } private Runnable runnable = new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " 启动了,准备获取锁"); reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + " 获取了锁"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } }; public static void main(String args[]) { TestThread testThread = new TestThread(); testThread.testLock(); } }
打印如下:
image.png
可以看出,线程2、3、4、5 按顺序获取锁,实际上拿到锁也是按照这顺序的。
因此,符合先到先得,是公平的。
再来验证非公平锁public class TestThread { private ReentrantLock reentrantLock = new ReentrantLock(false); private void testLock() { for (int i = 0; i < 10; i++) { Thread thread = new Thread(runnable); thread.setName("线程" + (i + 1)); thread.start(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } private void testUnfair() { try { Thread.sleep(500); while (true) { System.out.println("+++++++我抢...+++++++"); boolean isLock = reentrantLock.tryLock(); if (isLock) { System.out.println("========我抢到锁了!!!==========="); reentrantLock.unlock(); return; } Thread.sleep(10); } } catch (InterruptedException e) { e.printStackTrace(); } } private Runnable runnable = new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " 启动了,准备获取锁"); reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + " 获取了锁"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } }; public static void main(String args[]) { TestThread testThread = new TestThread(); testThread.testLock(); testThread.testUnfair(); } }
打印如下:
image.png
image.png
这两张图结合来看:
1、第一张图:线程1 线程10 依次调用lock抢锁,然后主线程开始抢锁。
2、只要有一次能够证明主线成比线程1 线程10之间的某个线程先获得锁,那么就证明该锁为非公平锁。
3、第二张图:主线程比线程4 线程10 先获得了锁,说明过程是非公平的。
值得注意的是:
此处使用tryLock()抢占锁,tryLock()和lock(非公平模式)核心逻辑是一样的。 3、底层如何获取锁/释放锁
一直在提线程获取了锁,线程释放了锁,到底这个逻辑如何实现的呢?
从第一张全家福的图,可以看出锁的基本数据结构包含:
共享锁变量、volatile、CAS、同步队列。
假设设定共享变量为:volatile int threadId。
threadId == 0表示当前没有线程获取锁,thread !=0 表示有线程占有了锁。
获取锁
1、线程调用 CAS(threadId, 0, 1),预期threadId == 0, 若是符合预期,则将threadId设置为1,CAS成功说明成功获取了锁。
2、若是CAS失败,说明threadId != 0,进而说明有已经有别的线程修改了threadId,因此线程获取锁失败,然后加入到同步队列。
释放锁
1、持有锁的线程不需要锁后要释放锁,假设是独占锁(互斥),因为同时只有一个线程能获取锁,因此释放锁时修改threadId不需要CAS,直接threadId == 0,说明释放锁成功。
2、成功后,唤醒在同步队列里等待的线程。
synchronized 和 AQS 获取/释放锁核心思想就是上面几步,只是控制得更复杂,精细,考虑得更全面。
注:CAS(threadId, xx, xx)是伪代码4、自旋锁与自适应自旋
很多文章说CAS是自旋锁,这说法是有问题的,本质上没有完全理解CAS功能和锁。
1、CAS 全称是比较与交换,若是内存值与期望值一致,说明没有其它线程更改目标变量,因此可以放心地将目标变量修改为新值。
2、CAS 是原子操作,底层是CPU指令。
3、CAS 只是一次尝试修改目标变量的操作,结果要么成功,要么失败,最后调用都会返回。
通过上个小结的分析,我们知道synchronized、AQS底层获取/释放锁都是依赖CAS的,难道说synchronized、AQS 也是自旋锁,显然不是。
自旋锁是不会阻塞的,而CAS也不会阻塞,因此可以利用CAS实现自旋锁: class MyLock { AtomicInteger atomicInteger = new AtomicInteger(0); private void lock() { boolean suc = false; do { //底层是CAS suc = atomicInteger.compareAndSet(0, 1); } while (!suc); } }
如上所示,自定义锁MyLock,线程1,线程2分别调用lock()上锁。
1、线程1调用lock(),因为atomicInteger== 0,所以suc == true,线程1成功获取锁。
2、此时线程2也调用lock(),因为atomicInteger==1,说明锁被占用了,所以suc==false,然而线程2并不阻塞,一直循环去修改。只要线程1不释放锁,那么线程2永远获取不了锁。
以上就是自旋锁的实现,可以看出:
1、自旋锁最大限度避免了线程挂起/与唤醒,避免上下文切换,但是无限制的自旋也会徒劳占用CPU资源。
2、因此自选锁适用于线程执行临界区比较快的场景,也就是获得锁后,快速释放了锁。
既想要自旋,又要避免无限制自旋,因此引入了自适应自旋: class MyLock { AtomicInteger atomicInteger = new AtomicInteger(0); //最大自旋次数 final int MAX_COUNT = 10; int count = 0; private void lock() { boolean suc = false; while (!suc && count <= MAX_COUNT) { //底层是CAS suc = atomicInteger.compareAndSet(0, 1); if (!suc) Thread.yield(); count++; } } }
可以看出,给自旋设置了最大自旋次数,若还是没能获取到锁,则退出死循环。
实际上synchronized、ReentrantReadWriteLock 等的实现里,同样为了尽量避免线程挂起/唤醒,在抢占锁的过程中也是采用了自旋(自适应自旋)的思想,但这只是它们锁实现的以小部分,它们并不是自旋锁。5、为什么需要等待/通知机制
先看独占锁的伪代码: //Thread1 myLock.lock(); { //临界区代码 } myLock.unLock(); //Thread2 myLock.lock(); { //临界区代码 } myLock.unLock();
Thread1、Thread2 互斥拿到锁后各干各的,互不干涉,相安无事。
若是现在Thread1、Thread2 需要配合做事,如: //Thread1 myLock.lock(); { //临界区代码 while (flag == false) wait(); //继续做事 } myLock.unLock(); //Thread2 myLock.lock(); { //临界区代码 flag = true; notify(); //继续做事 } myLock.unLock();
如上代码,Thread1需要判断flag == true才会往下运行,而这个值需要Thread2来修改,Thread1、Thread2 两者间有协作关系。于是Thread1需要调用wait 释放锁,并阻塞等待。Thread2在Thread1释放锁后拿到锁,修改flag,然后notify 唤醒Thread1(唤醒时机在Thread2执行完临界区代码并释放锁后)。Thread1 被唤醒后继续抢锁,然后判断flag==true,继续做事。
于是,Thread1、Thread2愉快配合完成工作。
为啥wait/notify 需要先获取锁呢?flag 是线程间共享变量,需要在并发条件下正确访问,因此需要锁。
音画双绝刷新视限!创维守护者G53惊艳亮相北京品鉴会4月13日,创维电视与一线时尚大刊ELLEMEN睿士在北京共同举办首场刷新视限创维电视2022春季新品品鉴会,创维全新发布的全通道120Hz高刷大屏矩阵首次在线下亮相。其中,创维G
小米12Ultra欢迎你!台积电骁龙85100mAh,不愧为国产机皇代表现在市场上的国产旗舰机型在设计上真是百花齐放,而且机身材质的创新也让智能手机发展至今仍旧让大家保持新鲜感,当然谈不上全面超越苹果,但和iPhone至少是互有优势,而且在国产旗舰上依
vivo首款折叠屏E5双屏120HZ最近vivo发布了首款折叠屏手机xfold,配备E52K屏幕,并支持内外屏120HZ。。5000万大底主摄,4800万广角,1200万人像及800万潜望摄像头。采用骁龙8Gen1处
智己阿里巴巴首款平民轿跑智己阿里巴巴首款平民轿跑上市,实现零排放零污染,不加油不充电,搭载IM智慧系统,激光雷达,无框电吸门,自动驾驶,军工级高精度贯穿导航,科技超前,多便宜自己看链长宽高(mm)5098
推荐一款github上开源的SSLVPN前言2022春节以来,新冠病毒又在全国各地爆发,很多公司都被迫安排员工转为线上远程办公的方式进行。相对如深信服之类昂贵的SSLVPN,最近无意中在github看到了这款免费的SSL
学好SpringMVC有我就够了,几分钟从学废到牛批哥MVC设计概述在早期JavaWeb的开发中,统一把显示层控制层数据层的操作全部交给JSP或者JavaBean来进行处理,我们称之为Model1出现的弊端JSP和JavaBean之间
灵魂社交软件揭面以前的手机只是作为一个与外界沟通联系的一个简单的通讯工具,人们主要是用它来打电话和发短信。社会的进步,科技的发展,也使人们对精神上有了更高的要求。随着移动互联网的发达,科技也让生活
极低配电脑虚幻5话说虚幻5发布了,据说小白都可以自己花不到3hour做个小demo,于是有了3分钟的热度,决定去试试。不过,电脑这配置着实有点让人不太放心,不过本来就是试试么公司的网络贼快,很快就
二手机你不知道的那些事儿有很多小伙伴私信我想了解二手手机,今天我就和大家分享一下二手手机,让大家在买二手机的时候可以少踩坑。大家可能在想为什么是少踩坑,因为二手手机市场的水太深了,一不留神就容易出大问题,
苹果屏下FaceID专利曝光,iPhone真曲面屏时代终于要来了吗?从iPhoneX开始,果粉就开始期待iPhone真全面屏手机的来临,每次在苹果的新品发布会前总会有传言称苹果即将发布真全面屏iPhone,但显然每次的结果都令粉丝失望。别说真全面屏
三星S20系列上手评测数据只是参考,让体验说真话首先,这篇文章来自于S20系列用户的真实评价,有一说一,小编只是搬运工。三星在数码圈摸爬滚打这么多年,它的实力大家有目共睹,为何S20系列被众人评为目前最值得掏钱的性价比旗舰,今天
韩国三星手机险胜苹果,重夺市场冠军,中国品牌在前5位中占3席中韩通讯社李子移民市场调查机构CounterPointResearch日前发布数据显示,三星电子去年以微弱优势险胜苹果,摘得全球智能手机市场销冠。三星去年推出的折叠屏智能手机Gal
神回复魅族投票没输过的原因,找到了机友们好啊,又到了愉悦的问答环节。前两天,机哥发了一篇魅族可能要被吉利收购的文章。果然啊,咱机友里的老煤油确实不少,大家都纷纷出来发表了自己直接对魅族的看法。有人说自己的魅蓝Not
花700块钱让NS输出4K画面?PhotoFast倍线器简单实测体验昨天笔者发文解释了一下,市面上那些所谓的画质倍线器到底是什么样的东西,我自己是2款网售画质倍线器的用户,近期看到了网上卖的一款针对NS优化的画质增强器,只要把它插在NS上再连接电视
黑峡谷发布X3ProX5Pro三模机械键盘售价429元起键盘厂商黑峡谷于1月21日发布了全新的X3ProX5Pro三模机械键盘。两款产品分别为87键108键配列设计,相比上一代产品进行了全面升级,拥有三种轴体可选。黑峡谷Pro系列键盘全
推动充电口融合统一,工信部已表明态度,苹果会妥协吗?苹果在2020年发布的iPhone12系列,让环保二字失去了本身的意义,为了所谓的环保名义,苹果却变相取消了标配充电器,消费者想充电还需要额外付费购买。很多网友心里都清楚,这完全就
奇瑞新能源大蚂蚁国补退坡不涨价大蚂蚁的外观方面,蚂蚁整体延续家族设计风格,前脸采用封闭式格栅,并配备点阵式镀铬元素装饰,进行点缀。而且这款车外观设计非常的漂亮,它的设计风格也是相当的不错。这台小型SUV在外形颜
原华为智能驾驶总裁苏箐离职,华为感谢苏箐对车BU所做的贡献鞭牛士1月26日消息,原华为智能驾驶总裁苏箐近日被传出离职的消息。华为方面表示,苏箐确已离开华为车BU,感谢苏箐对车BU所做的贡献。2021年7月,华为发布的一封人事任免文件显示,
华鑫证券给予蓝色光标买入评级20220126华鑫证券有限责任公司朱珠对蓝色光标进行研究并发布了研究报告营销业务夯实新技术有望助力主业再下一城,本报告对蓝色光标给出买入评级,当前股价为9。28元。蓝色光标(30
光电通信2021年度十大热词出炉2021年,光纤光缆行业在5G双千兆工业互联网政策等因素的推动下,逐渐走出发展低谷,向好发展,光电通信媒体梳理行业发展脉络,聚焦企业发展动态,特整理出2021年度十大热词。双千兆3
物联网的核心技术是什么?1。物联网概念是指射频识别(RFID)红外感应器全球定位系统激光扫描器等信息传感设备,通过物联网域名,将任何物品与互联网相连接,进行信息交换和通信,以实现智能化识别定位跟踪监控和管
水印相机哪个软件好用?推荐马克水印相机,适用于外勤的工作场景。时间地点打卡水印,都是不能修改的,且时间真实,并且照片都是存在云端的,非常实用,不占手机内存。很多软件都有水印,每一款软件的水印都有独特的特