线程的死锁、活锁和饥饿现象
目录
1、死锁
2、活锁
3、饥饿
一个资源应该单独使用一把锁。
比如,一个对象中有多个共享资源,但有多个线程需要使用其中的不同资源
此时如果把对象整体作为一把锁,那并发就很低。
可以考虑,把每个共享资源都单独拆出来,分别上锁,这样每个线程都能各取所需,提高了并发度。
坏处是,可能导致线程死锁。
线程的代码是有限的,但由于某种原因,线程一直执行不完,称为线程的活跃性。
活跃性有三种原因:死锁、活锁、饥饿。
1、死锁
1、死锁的定义
死锁:一组互相竞争资源的线程因互相等待获取对方的资源,导致线程一直阻塞的情况。
2、为什么会产生死锁
一个线程需要同时获取多把锁时,就容易引发死锁。
简单的例子:
- t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁
- t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁
它俩又不会释放自己已经持有的锁,一直耗着,产生了死锁。
总结
如果锁应用不当,造成“两个线程都在等待对方释放锁
”,那么它们会一直等待,无法运行,这就发生了死锁。
简单说,死锁问题是由两个或以上的线程并行执行时,因为争抢资源而造成互相等待造成的
。
3、死锁的四要素
只要同时满足这四个条件,就肯定会死锁:
- 互斥条件
- 持有并等待条件
- 不可剥夺条件
- 环路等待条件
4、避免死锁的三种思路
避免死锁,只需要破坏掉四大条件的其中一个即可。
互斥条件没办法破坏,本来锁的目的就是互斥
所以只需要破坏以下三项中的其中一项即可:
- 破坏持有并等待条件:一个线程必须
一次性申请所有的锁
,不能单独持有某一个锁 - 破坏不可剥夺条件:一个线程
获取不到锁时,就先主动释放持有的所有锁
- 破坏环路等待条件:规定各个线程
获取锁的顺序
注意:三种思路,但是在具体的场景中,每种做法的开销都是不同的,需要找到开销最低的方式。
一般来说:
- 破坏持有并等待需要死循环检查条件,而且锁的粒度也很大,一般不去使用。
- 破坏环路等待只需要规定加锁顺序,效率较高
5、避免死锁的三种实现方式
1、破坏持有并等待条件
一次性申请所有锁这个动作属于临界区,应该抽取出一个类来管理,让它作为锁,向外提供两个同步方法:同时获取所有锁、同时释放所有锁。
由于它要作为锁,所以这个对象必须是单例的。
在尝试申请所有资源时,使用while()死循环,注意要加上超时判断。
class Allocator {// 维护一个资源列表private List<Object> als = new ArrayList<>();// 一次性申请所有资源synchronized boolean apply(Object from, Object to){if(als.contains(from) || als.contains(to)){return false; } else {als.add(from);als.add(to); }return true;}// 归还资源synchronized void free(Object from, Object to){als.remove(from);als.remove(to);} }class Account {// actr应该为单例private Allocator actr;private int balance;// 转账void transfer(Account target, int amt){// 一次性申请转出账户和转入账户,直到成功while(!actr.apply(this, target))try{// 锁定转出账户synchronized(this){ // 锁定转入账户synchronized(target){if (this.balance > amt){this.balance -= amt;target.balance += amt;}}}} finally {actr.free(this, target)}} }
此处while()与synchronized锁粗粒度的区别
在申请“锁”时,两种方式都是串行的。
但是,while()在通过单例对象获取到全部资源后,只需要申请所需的资源,不会影响其他无关的同类操作,可以并行执行。
而synchronized锁粗粒度,在执行时也只能串行执行。
2、破坏不可抢占条件
线程在获取不到锁时,释放手头持有的锁。这一点Java在语言层面是做不到的,不过Java在SDK层面实现了。
因为 synchronized 申请资源的时候,如果申请不到,线程直接进入阻塞状态了。线程进入阻塞状态后啥都干不了,也释放不了线程已经占有的资源。
java.util.concurrent 包下的 Lock 是可以轻松解决这个问题的。
它提供了一个方法:tryLock(long, TimeUnit),在一段时间内尝试获取锁,如果最终没有获取到,就执行释放锁的逻辑。
3、破坏循环等待条件
破坏这个条件,需要对资源进行排序,然后按序申请资源,这样就能避免多个线程交叉加锁的情况。
比如转账,就在代码中写死,按照账户id从小到大依次加锁,就不会有问题。
2、活锁
1、什么是活锁
活锁是指,线程没有发生阻塞,但依然执行不下去的情况。
2、活锁的例子
如果两个线程互相改变对方的结束条件,就可能导致双方谁也无法结束。
比如这个程序:
public class TestLiveLock {static volatile int count = 10;static final Object lock = new Object();public static void main(String[] args) {new Thread(() -> {// 期望减到 0 退出循环while (count > 0) {sleep(0.2);count--;log.debug("count: {}", count);}}, "t1").start();new Thread(() -> {// 期望超过 20 退出循环while (count < 20) {sleep(0.2);count++;log.debug("count: {}", count);}}, "t2").start();} }
相当于一个抽水一个注水,水池永远不会空或者满
3、死锁与活锁的区别
- 死锁:两个线程在执行过程中阻塞住了
- 活锁:两个线程一直没有阻塞,但都无法停止运行
4、活锁的解决方案
增加随机的睡眠时间,将这样的两个线程错开执行,只要一方先运行完了,那么另一方也就能运行完
3、饥饿
1、什么是线程饥饿
饥饿指的是线程因无法访问所需资源而无法执行下去的情况:
- 在CPU繁忙时,如果一个线程优先级太低,就有可能遇到一直得不到执行
- 持有锁的线程,如果执行的时间过长,会导致其他阻塞的线程一直获取不到锁
2、线程饥饿的解决方案
有三种方案:
- 保证资源充足
- 公平地分配资源,如果有需求可以使用公平锁,不过效率较低,很少使用。
- 避免持有锁的线程长时间执行
线程的死锁、活锁和饥饿现象相关推荐
- 【无标题】线程学习(18)-多把锁下的线程问题,死锁,活锁,饥饿
多把锁的应用 减小锁粒度,提交并发度. package com.bo.threadstudy.four;import lombok.extern.slf4j.Slf4j;/*** 多把锁的情况,以及后 ...
- mysql 活锁_Go语言死锁、活锁和饥饿概述
本节我们来介绍一下死锁.活锁和饥饿这三个概念. 死锁 死锁是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状 ...
- 死锁、活锁、饥饿定位死锁解决死锁
文章目录 1. 死锁 2. 定位死锁 2.1 jstack工具使用 2.2 jconsole工具使用: 3. 解决死锁 3.1 哲学家就餐问题 4. 活锁 4.1 活锁原因 4.2 活锁解决 5. 饥 ...
- 深耕Java多线程 - 死锁、活锁、饥饿
文章目录 1. 什么是死锁? 2. 死锁产生的四个必要条件是什么? 3. 如何定位死锁? 3.1 jps+jstack 3.2 jconsole 4. 如何避免死锁? 5. 活锁 6. 饥饿 1. 什 ...
- 死锁、活锁和饥饿是什么意思?
写在前面 本文隶属于专栏<100个问题搞定Java并发>,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java并 ...
- java多线程中的死锁、活锁、饥饿、无锁都是什么鬼?
转载自 java多线程中的死锁.活锁.饥饿.无锁都是什么鬼? 死锁.活锁.饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常地执行下去了. 死锁 死锁 ...
- Java多线程——线程池的饥饿现象
概述 定长线程池的使用过程中会存在饥饿现象,也就是当多线程情况下,当池中所有线程都被占用后,被占用的线程又需要空闲线程去进行下一步的操作,此时又获取不到池中空闲的线程,此时就出现了饥饿现象. 示例 p ...
- java线程饥饿原理_java 多线程饥饿现象的问题解决方法
java 多线程饥饿现象的问题解决方法 当有线程正在读的时候,不允许写 线程写,但是允许其他的读线程进行读.有写线程正在写的时候,其他的线程不应该读写.为了防止写线程出现饥饿现象,当线程正在读,如果写 ...
- 什么是活锁和饥饿?及示例
活锁 任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试-失败-尝试-失败的过程. 处于活锁的实体是在不断的改变状态,活锁有可能自行解开. 死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资 ...
最新文章
- 2021-03-07 英文写作中的“so that“和“such that“
- ES6 Fetch API和Cookie相关的知识点
- Request load inbound error - COM_ATTRFRG_GEN 066
- 山石网科发布重磅容器安全产品“山石云铠”,云安全版图再下一城
- 查看scala变量数据类型_Scala文字,变量和数据类型| Scala编程教程
- 分支和循环结构的应用(习题)
- WordPress电影资源下载主题:zmovie
- 《硅谷钢铁侠》---- 读书笔记
- {黑科技}哔哩哔哩视频三倍速播放
- 麻省理工公开课人工智能笔记六
- Mac 重启服务→services
- gulp入门详解之基本操作
- 【CV学习笔记】图像预处理warpaffine
- CDO安装指南(centos7)
- 微信清除缓存的两种方法
- 配置类Configuration
- ta点读笔客户端_PIYO PEN点读笔=早教机+故事机+智能音箱+伴眠神器
- go ip过滤_「净网2020」!利用GOIP设备协助作案上百起的多名“帮凶”被抓!
- C++打卡19-【排序模板】归并排序
- 计算机组成原理——输入输出设备(Input Output Equip-ment)
热门文章
- 斧子演示(AxeSlide)网页演示端(asweb)
- java-net-php-python-springboot易家养牛管理系统2020计算机毕业设计程序
- 对象序列化和反序列化
- 微信小程序java开发流程分享
- php校园二手交易网站 毕业设计-附源码841148
- SD-WAN技术实现方案(细节)-企业侧
- 2022年区块链趋势分析:全球区块链解决方案支出将达到117亿美元
- APP - 微信拉黑和删除有什么不同?还是好友吗?
- java不可以修改的修饰语,在Java程序中,用关键字_修饰的常量对象创建后就不能再修改了。...
- .net 2.0 中 DataTable 小兵变大将