什么是线程死锁?形成条件是什么?如何避免?
2020-05-29 3251
简介: 线程死锁的形成原因以及如何避免
什么是线程死锁
死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
线程死锁
下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》输出结果
public class DeadLockDemo { private static Object resource1 = new Object();//资源 1 private static Object resource2 = new Object();//资源 2 public static void main(String[] args) { new Thread(() -> { synchronized (resource1) { System.out.println(Thread.currentThread() + "get resource1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get resource2"); synchronized (resource2) { System.out.println(Thread.currentThread() + "get resource2"); } } }, "线程 1").start(); new Thread(() -> { synchronized (resource2) { System.out.println(Thread.currentThread() + "get resource2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get resource1"); synchronized (resource1) { System.out.println(Thread.currentThread() + "get resource1"); } } }, "线程 2").start(); } }
输出结果
Thread[线程 1,5,main]get resource1 Thread[线程 2,5,main]get resource2 Thread[线程 1,5,main]waiting get resource2 Thread[线程 2,5,main]waiting get resource1
线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到CPU执行权,然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
形成死锁的四个必要条件是什么
(1)互斥条件:线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放
(2)请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
(4)循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞
如何避免线程死锁
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件
一次性申请所有的资源。
破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
我们对线程 2 的代码修改成下面这样就不会产生死锁了。
new Thread(() -> { synchronized (resource1) { System.out.println(Thread.currentThread() + "get resource1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get resource2"); synchronized (resource2) { System.out.println(Thread.currentThread() + "get resource2"); } } }, "线程 2").start();
输出结果
Thread[线程 1,5,main]get resource1我们分析一下上面的代码为什么避免了死锁的发生?
我们分析一下上面的代码为什么避免了死锁的发生?欢迎关注公种浩:程序员追风,领取一线大厂Java面试题总结+各知识点学习思维导图+一份300页pdf文档的Java核心知识点总结!
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
有关操作系统的有:死锁,进程线程,中断,信号量等等。下面给出整理的一些常见的操作系统常见面试题
1. 请分别简单说一说进程和线程以及它们的区别。
–进程是具有一定功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源调
度和分配的一个独立单位
–线程是进程的实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本
单位
–一个进程可以有多个线程,多个线程也可以并发执行
2. 进程的通信方式有哪些?
–共享存储、消息传递、管道通信
3. 什么是缓冲区溢出?
–缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上
–危害有两点:程序崩溃,导致拒绝额服务, 跳转并且执行一段恶意代码
4. 什么是死锁?死锁产生的条件? 如何解决
–就是两个或多个进程无限期的阻塞、相互等待的一种状态。
–死锁产生条件
互斥条件:一个资源一次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放
不剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的环形等待资源关系
–解决死锁的基本方法: 预防死锁、避免死锁、检测死锁、解除死锁
5. 进程有哪几种状态? 如何相互转化?
–状态
就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源
运行状态:占用处理机资源运行,处于此状态的进程数小于等于 CPU 数
阻塞状态:进程等待某种条件,在条件满足之前无法执行
–状态转换
就绪状态 -> 运行状态:处于就绪状态的进程被调度后,获得处理机资源
运行状态 -> 就绪状态:处于运行状态的进程在时间片用完后,有更高优先级的进程就绪时,
运行状态 -> 阻塞状态:进程请求或等待某一事件发生时,就从运行状态转换为阻塞状态
阻塞状态 -> 就绪状态:当进程等待的事件到来时,如 I/O 操作结束或中断结束时,中断处
理程序必须把相应进程的状态由阻塞状态转换为就绪状态。
6. 什么是临界区? 进程同步? 如何解决冲突?
–每个进程中访问临界资源的那段程序称为临界区
–是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源
和相互合作,从而使程序的执行具有可再现性
–同步机制遵循的原则:
(1)空闲让进;
(2)忙则等待(保证对临界区的互斥访问);
(3)有限等待(有限代表有限的时间,避免死等);
(4)让权等待,(当进程不能进入自己的临界区时,应释放处理机,以免陷入忙等状态)
7. 操作系统中进程调度策略有哪几种?
–先来先服务, 短作业优先调度算法、 高相应比算法、 时间片轮转、 多级反馈队列调度算法
8. 什么是中断?
–在计算机执行期间,系统内发生急需处理事件,使得 CPU 暂时中断当前执行程序而转去执行相应的事件处理程序, 待处理完毕后又返回原来被中断处继续执行的过程
9. 操作系统的主要组成部分(操作系统的基本功能) ?
–进程和线程的管理,存储管理,设备管理,文件管理
10. 操作系统的四个特性
–并发:同一段时间内多个程序执行(注意区别并行和并发,前者是同一时刻的多个事件,后
者是同一时间段内的多个事件)
–共享:系统中的资源可以被内存中多个并发执行的进线程共同使用
–虚拟:通过时分复用(如分时系统)以及空分复用(如虚拟内存)技术实现把一个物理实
体虚拟为多个
–异步:系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进
11. 动态分区分配时所采用的几种算法
–首次适应(First Fit)算法
空闲分区以地址递增的次序链接。分配内存时顺序查找,找到能满足要求的第一个空闲分区
–最佳适应(Best Fit)算法
空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区
–最坏适应(Worst Fit)算法
又称最大适应(Largest Fit)算法,空闲分区以容量递减的次序链接。找到第一个能满足要求的
空闲分区,也就是挑选出最大的分区
12. 虚拟内存, 虚拟存储器的特征
–基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其余部分留在外存,
当所访问的信息不在内存时,由操作系统将所需要部分调入内存,另一方面,将内存中暂时
不使用的内容换出到外存上。这样,系统好像为用户提供了一个比实际内存大得多的存储器,
称为虚拟存储器
–特性
多次性:一个作业可以分多次被调入内存。
对换性:作业运行过程中存在换进换出的过程
虚拟性:虚拟性体现在其从逻辑上扩充了内存的容量的应用程序,虚拟性建立在多次性和对
换性的基础上行,多次性和对换性又建立在离散分配的基础上
13. 分页和分段有什么区别?
–段是信息的逻辑单位,它是根据用户的需要划分的,用户是可见的 , 页是信息的物理单位,
是为了管理主存的方便而划分的,对用户是透明的
–段的大小不固定,有它所完成的功能决定;页大大小固定, 由系统决定
–段向用户提供二维地址空间;页向用户提供的是一维地址空间
–段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制
20.什么是用户态和内核态
为了限制不同程序的访问能力,防止一些程序访问其它程序的内存数据,CPU划分了用户态和内核态两个权限等级。
用户态只能受限地访问内存,且不允许访问外围设备,没有占用CPU的能力,CPU资源可以被其它程序获取;
内核态可以访问内存所有数据以及外围设备,也可以进行程序的切换。
所有用户程序都运行在用户态,但有时需要进行一些内核态的操作,比如从硬盘或者键盘读数据,这时就需要进行系统调用,使用陷阱指令,CPU切换到内核态,执行相应的服务,再切换为用户态并返回系统调用的结果。
为什么要分用户态和内核态?
安全性:防止用户程序恶意或者不小心破坏系统/内存/硬件资源;
封装性:用户程序不需要实现更加底层的代码;
利于调度:如果多个用户程序都在等待键盘输入,这时就需要进行调度;统一交给操作系统调度更加方便。
2. 如何从用户态切换到内核态?
系统调用:比如读取命令行输入。本质上还是通过中断实现
用户程序发生异常时:比如缺页异常
外围设备的中断:外围设备完成用户请求的操作之后,会向CPU发出中断信号,这时CPU会转去处理对应的中断处理程序
*** 进程同步有哪几种机制
原子操作、信号量机制、自旋锁管程、会合、分布式系统
什么是颠簸现象?
颠簸本质上是指频繁的页调度行为。进程发生缺页中断时必须置换某一页。然而,其他所有的页都在使用,它置换一个页,但又立刻再次需要这个页。因此会不断产生缺页中断,导致整个系统的效率急剧下降,这种现象称为颠簸。内存颠簸的解决策略包括:
修改页面置换算法;
降低同时运行的程序的数量;
终止该进程或增加物理内存容量。
————————————————
版权声明:本文为CSDN博主「wf0934」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。