李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
21.并发编程之活跃性分析
Leefs
2022-10-24 PM
1142℃
0条
[TOC] ### 一、死锁 #### 死锁产生的四个必要条件 + **互斥条件**:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。 + **请求和保持条件**:当进程因请求资源而阻塞时,对已获得的资源保持不放。 + **不剥夺条件**:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。 + **环路等待条件**:在发生死锁时,必然存在一个进程–资源的环形链。 #### 死锁示例 有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁 + t1 线程获得A对象锁,接下来想获取 B对象的锁 + t2 线程获得B对象锁,接下来想获取 A对象的锁 ```java import lombok.extern.slf4j.Slf4j; import static com.lilinchao.thread.utils.Sleeper.sleep; /** * @author lilinchao * @date 2022-10-24 * @description 死锁 **/ @Slf4j(topic = "c.TestDeadLock") public class TestDeadLock { public static void main(String[] args) { test1(); } private static void test1() { Object A = new Object(); Object B = new Object(); Thread t1 = new Thread(() -> { synchronized (A) { log.debug("lock A"); sleep(1); synchronized (B) { log.debug("lock B"); log.debug("操作..."); } } },"t1"); Thread t2 = new Thread(() -> { synchronized (B) { log.debug("lock B"); sleep(0.5); synchronized (A) { log.debug("lock A"); log.debug("操作..."); } } },"t2"); t1.start(); t2.start(); } } ``` **运行结果** ``` 21:51:08.966 c.TestDeadLock [t2] - lock B 21:51:08.966 c.TestDeadLock [t1] - lock A // 无限等待 ``` ### 二、定位死锁 检测死锁可以使用`jconsole`工具,或者使用jps定位进程id,再用`jstack` 定位死锁。 #### 2.1 jconsole工具检测死锁 **(1)选择要监测死锁的进程** ![21.并发编程之活跃性分析01.jpg](https://lilinchao.com/usr/uploads/2022/10/3488772708.jpg) **(2)选择线程->点击下方检测死锁按钮** ![21.并发编程之活跃性分析02.jpg](https://lilinchao.com/usr/uploads/2022/10/3758124127.jpg) **(3)产生死锁的线程和信息** ![21.并发编程之活跃性分析03.jpg](https://lilinchao.com/usr/uploads/2022/10/2686760923.jpg) #### 2.2 通过`jstack` 命令定位死锁 **(1)通过`jps`命令定位进程id** ```basic D:\Codes\idea\thread_demo>jps 7456 JConsole 9156 TestDeadLock 1128 Launcher 12264 Jps ``` **(2)通过 `jstack` 命令定位死锁** ``` D:\Codes\idea\thread_demo>jstack 9156 ``` **运行部分结果** ``` ............... Java stack information for the threads listed above: =================================================== "t2": at com.lilinchao.concurrent.demo_03.TestDeadLock.lambda$test1$1(TestDeadLock.java:37) - waiting to lock <0x00000000d7f90678> (a java.lang.Object) - locked <0x00000000d7f90688> (a java.lang.Object) at com.lilinchao.concurrent.demo_03.TestDeadLock$$Lambda$2/1416233903.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "t1": at com.lilinchao.concurrent.demo_03.TestDeadLock.lambda$test1$0(TestDeadLock.java:26) - waiting to lock <0x00000000d7f90688> (a java.lang.Object) - locked <0x00000000d7f90678> (a java.lang.Object) at com.lilinchao.concurrent.demo_03.TestDeadLock$$Lambda$1/787387795.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock. ``` + 避免死锁要注意加锁顺序 + 另外如果由于某个线程进入了死循环,导致其它线程一直等待 对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 `top -Hp` 进程id 来定位是哪个线程,最后再用 jstack 排查 ### 三、哲学家就餐问题 ![21.并发编程之活跃性分析04.jpg](https://lilinchao.com/usr/uploads/2022/10/1142354574.jpg) 有五位哲学家,围坐在圆桌旁。 + 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。 + 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。 + 如果筷子被身边的人拿着,自己就得等待。 **代码示例** ```java import com.lilinchao.concurrent.utils.Sleeper; import lombok.extern.slf4j.Slf4j; /** * Created by lilinchao * Date 2022/10/24 * Description 哲学家就餐问题 */ public class Test05 { public static void main(String[] args) { Chopstick c1 = new Chopstick("1"); Chopstick c2 = new Chopstick("2"); Chopstick c3 = new Chopstick("3"); Chopstick c4 = new Chopstick("4"); Chopstick c5 = new Chopstick("5"); new Philosopher("苏格拉底", c1, c2).start(); new Philosopher("柏拉图", c2, c3).start(); new Philosopher("亚里士多德", c3, c4).start(); new Philosopher("赫拉克利特", c4, c5).start(); new Philosopher("阿基米德", c5, c1).start(); } } @Slf4j(topic = "c.Philosopher") class Philosopher extends Thread { Chopstick left; Chopstick right; public Philosopher(String name, Chopstick left, Chopstick right) { super(name); this.left = left; this.right = right; } @Override public void run() { while (true) { // 尝试获得左手筷子 synchronized (left) { // 尝试获得右手筷子 synchronized (right) { eat(); } } } } private void eat() { log.debug("eating..."); // 吃饭 Sleeper.sleep(0.5); // 思考 } } class Chopstick { String name; public Chopstick(String name) { this.name = name; } @Override public String toString() { return "筷子{" + name + '}'; } } ``` **运行结果** ``` 22:54:29.039 c.Philosopher [赫拉克利特] - eating... 22:54:29.039 c.Philosopher [苏格拉底] - eating... 22:54:29.544 c.Philosopher [亚里士多德] - eating... 22:54:29.544 c.Philosopher [阿基米德] - eating... 22:54:30.045 c.Philosopher [阿基米德] - eating... 22:54:30.545 c.Philosopher [赫拉克利特] - eating... 22:54:31.046 c.Philosopher [亚里士多德] - eating... // 无限等待 ``` **使用 jconsole 检测死锁** ![21.并发编程之活跃性分析05.jpg](https://lilinchao.com/usr/uploads/2022/10/1500029961.jpg) 线程各自持有各自的资源无法释放。 这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况。 ### 四、活锁 活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。 **示例** ```java import lombok.extern.slf4j.Slf4j; import static com.lilinchao.concurrent.utils.Sleeper.sleep; /** * @author lilinchao * @date 2022-10-24 * @description 活锁 **/ @Slf4j(topic = "c.TestLiveLock") 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(); } } ``` **运行结果** ``` 23:04:21.041 c.TestLiveLock [t1] - count: 9 23:04:21.041 c.TestLiveLock [t2] - count: 9 23:04:21.246 c.TestLiveLock [t2] - count: 10 23:04:21.246 c.TestLiveLock [t1] - count: 9 23:04:21.447 c.TestLiveLock [t2] - count: 10 23:04:21.447 c.TestLiveLock [t1] - count: 9 23:04:21.652 c.TestLiveLock [t2] - count: 10 ...................... ``` **分析** t1和t2两个线程,一个执行`count--`,期望减到0的时候退出循环,一个执行`count++`,期望加到20退出循环,但是永远不能都退出循环。 ### 五、饥饿 很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题 下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题 **死锁的产生** ![21.并发编程之活跃性分析06.jpg](https://lilinchao.com/usr/uploads/2022/10/308838594.jpg) - 线程1获取到了锁A,线程2获取到了锁B - 线程1尝试获取锁B失败,线程2尝试获取锁A失败 **顺序加锁的解决方案来避免死锁** ![21.并发编程之活跃性分析07.jpg](https://lilinchao.com/usr/uploads/2022/10/3428327725.jpg) - 线程1获取到了锁A,线程2尝试获取锁A失败 - 线程1获取到了锁B,线程2尝试获取锁B将会被阻塞
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2520.html
上一篇
20.并发编程之多把锁问题
下一篇
22.并发编程之ReentrantLock简介
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
数学
Nacos
线程池
Http
NIO
持有对象
Hbase
数据结构和算法
链表
Livy
Scala
队列
并发线程
Flink
并发编程
MyBatis-Plus
容器深入研究
散列
GET和POST
Golang基础
Hadoop
Redis
Flume
Sentinel
Azkaban
Yarn
MyBatis
Spark SQL
ajax
Stream流
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭