活跃性分析

简介: 活跃性分析

一、死锁

死锁产生的四个必要条件

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

死锁示例

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

  • 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 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题

目录
相关文章
|
安全 架构师 网络协议
微内核架构
微内核架构
|
机器学习/深度学习 数据可视化 数据挖掘
探索大数据时代的关键技术:数据挖掘、可视化和数据仓库
探索大数据时代的关键技术:数据挖掘、可视化和数据仓库
1031 0
|
消息中间件 容灾 Java
Windows 下安装RocketMQ的两种方式
一 , Windows 下借助Docker 安装
Windows 下安装RocketMQ的两种方式
|
机器学习/深度学习 传感器 自动驾驶
自动驾驶技术中的模仿学习
自动驾驶中的模仿学习是一种关键技术,用于使自动驾驶系统能够学习和模仿人类驾驶行为。通过模仿驾驶员的操作,自动驾驶车辆可以在复杂的道路环境中实现类似人类的驾驶决策和操作,从而提升安全性、舒适性和可靠性。
249 3
|
Java API Spring
Spring Boot中的API版本控制
Spring Boot中的API版本控制
|
机器学习/深度学习 存储 自然语言处理
深度学习中的模型压缩技术
在现代深度学习应用中,模型的复杂性和体积不断增加,给存储和计算带来了巨大的挑战。为了解决这些问题,模型压缩技术应运而生,并成为研究热点。本文将介绍什么是模型压缩技术,探讨其常见方法及应用实例,分析其在实际应用中的效果和前景。
241 1
|
安全 前端开发 Java
学习从Struts迁移到Spring的策略概述
从Struts框架迁移到Spring框架是一个常见的升级路径,主要是为了利用Spring框架提供的更多功能、更好的模块化支持以及更广泛的社区资源。
266 3
计算机网络:编码与调制
计算机网络:编码与调制
299 0
|
人工智能 供应链 搜索推荐
智能AI在零售领域的应用
智能AI在零售领域的应用
|
数据挖掘 数据库
R实战 | 倾向性评分匹配(PSM)
R实战 | 倾向性评分匹配(PSM)
333 0