活跃性分析

简介: 活跃性分析

一、死锁

死锁产生的四个必要条件

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

死锁示例

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁

  • t1 线程获得A对象锁,接下来想获取 B对象的锁
  • t2 线程获得B对象锁,接下来想获取 A对象的锁
import lombok.extern.slf4j.Slf4j;
import static site.weiyikai.thread.utils.Sleeper.sleep;
/**
 * @author xiaowei
 * @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)选择要监测死锁的进程

(2)选择线程->点击下方检测死锁按钮

(3)产生死锁的线程和信息

2.2 通过jstack 命令定位死锁

(1)通过jps命令定位进程id

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 排查

三、哲学家就餐问题

有五位哲学家,围坐在圆桌旁。

  • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
  • 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
  • 如果筷子被身边的人拿着,自己就得等待
import site.weiyikai.concurrent.utils.Sleeper;
import lombok.extern.slf4j.Slf4j;
/**
 * Created by xiaowei
 * 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 检测死锁

线程各自持有各自的资源无法释放。

这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况。

四、活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。

活锁是一种情景,当两个或多个线程不停地尝试执行某个操作,但它们的执行被彼此的操作阻塞时,就会出现活锁。例如,在一个多人游戏中,两个角色相遇,他们的行动互相阻止对方移动,最终引发活锁。


在这种情况下,两个线程可以一直卡在互相阻止对方的操作上,导致无法结束程序。解决活锁的方法是引入一些调度机制,在两个线程中有一方先停止操作,让另一方继续执行,然后再轮到其它方停止操作。这样,两个线程可以在不互相阻塞的时候完成任务,从而避免了活锁的发生。


因此,在编程时,需要遵循一些规范,避免出现活锁的情况,以保证线程的正常运行。

import lombok.extern.slf4j.Slf4j;
import static site.weiyikai.concurrent.utils.Sleeper.sleep;
/**
 * @author xiaowei
 * @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 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题

目录
相关文章
|
5月前
|
缓存 并行计算 安全
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析
|
5月前
|
安全 程序员
多线程的6个综合练习
多线程的6个综合练习
34 0
|
6月前
|
监控 安全
线程死循环是多线程应用程序开发过程中一个难以忽视的问题,它源于线程在执行过程中因逻辑错误或不可预见的竞争状态而陷入永久运行的状态,严重影响系统的稳定性和资源利用率。那么,如何精准定位并妥善处理线程死循环现象,并在编码阶段就规避潜在风险呢?谈谈你的看法~
避免线程死循环的关键策略包括使用同步机制(如锁和信号量)、减少共享可变状态、设置超时、利用监控工具、定期代码审查和测试、异常处理及设计简洁线程逻辑。通过这些方法,可降低竞态条件、死锁风险,提升程序稳定性和可靠性。
102 0
|
数据采集 Python
python多线程并发采集黄金走势数据
python使用aiohttp 通过设置代理IP,实现多线程并发采集
python多线程并发采集黄金走势数据
|
并行计算 安全 算法
Oh!老伙计,提高自己的并发技能,先从锁优化开始吧
锁是最常用的同步方法之一。在高并发的环境下,激烈的锁竞争会导致程序的性能下降。 对于单任务或者单线程的应用而言,其主要资源消耗都花在任务本身,它既不需要维护并行数据结构间的一致性状态,也不需要为线程的切换和调度花费时间。对于多线程应用来说,系统除了处理功能需求外,还需要额外维护多线程环境的特有信息,如线程本身的元数据、线程的调度、线程上下文的切换等。并行计算之所以能提高系统的性能,并不是因为它"少干活"了,而是因为并行计算可以更合理地进行任务调度,充分利用各个CPU资源。
|
SQL 监控 关系型数据库
死锁分析延续
可知上一篇【死锁分析】,又重新表达了一些图片,图画更了
153 0
死锁分析延续
|
Java
分析一个常见的java多线程通信问题(假死现象)
一件复杂的事,一个人如果不能做,两个人又做的不好,一群人就可能很好的解决了。对于线程来说也是,通过多个线程就能完成一个更复杂的功能,这就需要多个线程协作,协作就需要交流,但是交流总是会出问题的。在这篇文章中我们分析一下java多线程通信过程中出现的一个假死现象。然后给出一个解决办法
559 0
分析一个常见的java多线程通信问题(假死现象)
【多线程:综合练习】
【多线程:综合练习】
77 0
|
缓存 监控 算法
漫画:性能、可用性和锁
经过了几天的熟悉环境,小鱼开始让飞鸟尝试负责解决一些问题。分配的第一个问题现象是这样的: 接口有偶尔的超时现象。平时的时候接口可以在2s响应调用的上游。但是偶尔会有几次,超时特别严重,有时候20s、30s才返回。但是上游的超时时间是10s。所以这时候上游是拿不到结果的。对上游来说这次请求失败了。 飞鸟通过排查,找到了一些线索,就抱着电脑去找小鱼。
漫画:性能、可用性和锁
|
Java 程序员
一个多线程死锁案例,如何避免及解决死锁问题?
多线程死锁在java程序员笔试的时候时有遇见,死锁概念在之前的文章有介绍,大家应该也都明白它的概念,不清楚的去翻看历史文章吧。
216 0
一个多线程死锁案例,如何避免及解决死锁问题?

相关实验场景

更多
下一篇
无影云桌面