| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 【刨根问底】带你深入理解JUC并发工具类 — 信号量和管程 -> 正文阅读 |
|
[Java知识库]【刨根问底】带你深入理解JUC并发工具类 — 信号量和管程 |
大家好,我是Java不惑(WX公众号同名),这是专栏的第三篇文章。 在前面的两篇文章中,我介绍了volatile、cas以及其在处理器中的实现。 我们需要知道,volatile和cas是最基础的工具,实际的业务场景中共享变量的同步问题是非常复杂的,所以很少直接使用它们来处理同步问题。 基于这个工具上面我们可以封装成“锁”,方便我们解决多线程同步问题。今天我会讲一下基于volatile+cas实现的同步机制,也就是信号量和管程。 信号量
信号量在《计算机操作系统》一书中应该已经学过了,下面我再简单介绍一下。 信号量可以看成特殊的变量,可以对它进行增加和减少的操作,并且是原子性的。 整型信号量首先我看先看一下整型信号量,整型信号量定义为表示资源数目的整型量S,仅能通过原子操作P、V操作。 当线程想要使用共享变量时,会先获取信号量,如果拿到信号量,才被允许使用共享变量。
举个例子解释一下:假设有一间会议室,因为会议室椅子有限,所以容纳的参会人员有限(不允许人站着),这时我们把会议室当做共享变量,参会人员当做线程。为了防止多个人(线程)同时进入会议室(共享变量),所以主办方在会议室门口放了一个盒子,盒子里面是一些门票(信号量S),小盒只允许一个人伸手拿门票(原子性),拿到门票就代表可以进入会议室。 当盒子里只有一张门票时(信号量S值为1),会议室(共享变量)只允许一个人(线程)进入,此时会议室(共享变量)是互斥访问的。 好了,上面就是整型信号量的介绍,使用整型信号量,我们就实现了一把非常简单的锁。我们抽象出来了门票的概念,想要同步的访问共享变量只需要获取到门票(信号量S)就可以了。 记录型信号量上面的整型信号量是非常简陋的,并且还存在缺陷。比如在wait操作中,只要S<=0就会不断地循环,多个线程访问同一信号量,会严重消耗CPU资源。 解决方法也很简单,我们增加一个链表(队列)L,进程获取信号量资源失败,会调用block原语自我阻塞,并插入链表L中等待。 当其他线程释放信号量时,会执行signal操作,释放资源,这时链表L中如果存在进程,则调用wakeup原语,将链表中第一个等待进程唤醒,等待中的线程就会去尝试获取信号量。 AND型信号量上述问题是解决多个进程共享同一个共享变量,如果多个进程共享多个共享变量,会出现死锁。 例如下面的例子,进程A获取S1的资源,进程B获取S2的资源,当进程A想要获取资源S2时,会因为获取不到而发生阻塞,这时进程B想要获取S1资源。因此在无外力作用下,两者都无法在阻塞状态中恢复而发生死锁。
AND同步机制的思想是:将进程在整个运行过程中所需要的资源,一致性分配给进程,如果有一个资源未能分配给进程,其他资源也需要释放。
信号量可以解决并发中的同步问题,Java中仅JUC并发工具类中的Semaphore实现了信号量。 Java中的Semaphore用处并不大,多个线程同时访问临界区,同时进入的线程也会导致原子性问题。当然可以设置为仅允许一个线程进入,但这种情况下使用管程可能会更好。 Java中基于管程实现了锁,下面我们看一下管程是什么,以及在Java中的实现。 管程
对于管程,我们直接看下图(图片来源网络): 首先当多个线程想要进入临界区时,会首先获取到锁,如果获取不到锁就进入同步队列中,同步队列中的线程等待其他线程释放锁。拿到锁之后进入管程,进入后会判断条件变量(condition),如果不符合条件会进入等待队列中,要想再次获取锁,必须等待其他线程调用notify唤醒等待队列中的第一个线程,并进入同步队列中排队;或者等待其他线程调用notifyAll将所有等待队列中的线程放入同步队列中。 是不是想到了什么,Java中的synchronized就是使用了管程。 synchronized和管程synchronized中,和上图不同,它仅存在一个条件变量(condition)。 Java中每个对象对应一个管程(monitor),所以在Object类中存在wait() 、notify()和notifyAll ()等方法都属于对象中的管程。
如上面代码所示,线程进入管程时,会先加锁,如果获取到了锁就进入管程,如果获取不到锁,就进入同步队列中。 那么synchronized中的锁是怎么实现的呢?monitor中存在两个变量,一个是_owner变量,这个变量保存是哪个线程进入了管程,这个变量和信号量S是比较像的。因为synchronized是可重入锁,所以需要有一个计数器用于保存重入的次数,计数器这个变量是:count。 获取到锁之后,进入管程,也就是上面代码中synchronized修饰的大括号。 进入管程后,可以使用while()判断条件变量是否满足。当然在实际的开发过程中,条件变量(condition)使用次数还是比较少的。 如果条件不满足,会调用Object对象中的wait()方法进入等待队列中。 如果其他线程满足条件,调用notifyAll()后,会 将等待队列中的线程放入同步队列中,争用到锁之后进入管程继续执行。 上面的过程也解释了为什么wait() 、notify()和notifyAll ()需要获取到锁才能执行。 总结在这篇文章中,我介绍了信号量和管程,以及管程在Java中的实现。希望你看完有所收获,受限于个人水平,文章若有错漏,还望读者不吝赐教。 在接下来的文章中,我将进入这个专栏的主题,也就是JUC并发类相关的介绍和实现。 最后,如果我的文章对你有帮助,请帮我点赞转发! 如果你对volatile和cas不熟悉,可以看第一篇文章《【刨根问底】带你深入理解JUC并发工具类 — volatile和cas》; 如果你向了解一下valatile的实现原理,可以看一下第二篇文章《【刨根问底】带你深入理解JUC并发工具类 — 缓存一致性和内存屏障》 也可以访问该专栏的导航文章:《【刨根问底】带你深入理解JUC并发工具类 — 开篇》 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 13:01:14- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |