目录

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、线程饥饿的解决方案

有三种方案:

  • 保证资源充足
  • 公平地分配资源,如果有需求可以使用公平锁,不过效率较低,很少使用。
  • 避免持有锁的线程长时间执行

线程的死锁、活锁和饥饿现象相关推荐

  1. 【无标题】线程学习(18)-多把锁下的线程问题,死锁,活锁,饥饿

    多把锁的应用 减小锁粒度,提交并发度. package com.bo.threadstudy.four;import lombok.extern.slf4j.Slf4j;/*** 多把锁的情况,以及后 ...

  2. mysql 活锁_Go语言死锁、活锁和饥饿概述

    本节我们来介绍一下死锁.活锁和饥饿这三个概念. 死锁 死锁是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状 ...

  3. 死锁、活锁、饥饿定位死锁解决死锁

    文章目录 1. 死锁 2. 定位死锁 2.1 jstack工具使用 2.2 jconsole工具使用: 3. 解决死锁 3.1 哲学家就餐问题 4. 活锁 4.1 活锁原因 4.2 活锁解决 5. 饥 ...

  4. 深耕Java多线程 - 死锁、活锁、饥饿

    文章目录 1. 什么是死锁? 2. 死锁产生的四个必要条件是什么? 3. 如何定位死锁? 3.1 jps+jstack 3.2 jconsole 4. 如何避免死锁? 5. 活锁 6. 饥饿 1. 什 ...

  5. 死锁、活锁和饥饿是什么意思?

    写在前面 本文隶属于专栏<100个问题搞定Java并发>,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java并 ...

  6. java多线程中的死锁、活锁、饥饿、无锁都是什么鬼?

    转载自 java多线程中的死锁.活锁.饥饿.无锁都是什么鬼? 死锁.活锁.饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常地执行下去了. 死锁 死锁 ...

  7. Java多线程——线程池的饥饿现象

    概述 定长线程池的使用过程中会存在饥饿现象,也就是当多线程情况下,当池中所有线程都被占用后,被占用的线程又需要空闲线程去进行下一步的操作,此时又获取不到池中空闲的线程,此时就出现了饥饿现象. 示例 p ...

  8. java线程饥饿原理_java 多线程饥饿现象的问题解决方法

    java 多线程饥饿现象的问题解决方法 当有线程正在读的时候,不允许写 线程写,但是允许其他的读线程进行读.有写线程正在写的时候,其他的线程不应该读写.为了防止写线程出现饥饿现象,当线程正在读,如果写 ...

  9. 什么是活锁和饥饿?及示例

    活锁 任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试-失败-尝试-失败的过程. 处于活锁的实体是在不断的改变状态,活锁有可能自行解开. 死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资 ...

最新文章

  1. 2021-03-07 英文写作中的“so that“和“such that“
  2. ES6 Fetch API和Cookie相关的知识点
  3. Request load inbound error - COM_ATTRFRG_GEN 066
  4. 山石网科发布重磅容器安全产品“山石云铠”,云安全版图再下一城
  5. 查看scala变量数据类型_Scala文字,变量和数据类型| Scala编程教程
  6. 分支和循环结构的应用(习题)
  7. WordPress电影资源下载主题:zmovie
  8. 《硅谷钢铁侠》---- 读书笔记
  9. {黑科技}哔哩哔哩视频三倍速播放
  10. 麻省理工公开课人工智能笔记六
  11. Mac 重启服务→services
  12. gulp入门详解之基本操作
  13. 【CV学习笔记】图像预处理warpaffine
  14. CDO安装指南(centos7)
  15. 微信清除缓存的两种方法
  16. 配置类Configuration
  17. ta点读笔客户端_PIYO PEN点读笔=早教机+故事机+智能音箱+伴眠神器
  18. go ip过滤_「净网2020」!利用GOIP设备协助作案上百起的多名“帮凶”被抓!
  19. C++打卡19-【排序模板】归并排序
  20. 计算机组成原理——输入输出设备(Input Output Equip-ment)

热门文章

  1. 斧子演示(AxeSlide)网页演示端(asweb)
  2. java-net-php-python-springboot易家养牛管理系统2020计算机毕业设计程序
  3. 对象序列化和反序列化
  4. 微信小程序java开发流程分享
  5. php校园二手交易网站 毕业设计-附源码841148
  6. SD-WAN技术实现方案(细节)-企业侧
  7. 2022年区块链趋势分析:全球区块链解决方案支出将达到117亿美元
  8. APP - 微信拉黑和删除有什么不同?还是好友吗?
  9. java不可以修改的修饰语,在Java程序中,用关键字_修饰的常量对象创建后就不能再修改了。...
  10. .net 2.0 中 DataTable 小兵变大将