锁的活跃性

本文详细介绍了Java并发编程中的死锁、活锁和饥饿问题。通过实例代码展示了死锁如何发生,并提供了使用jconsole进行死锁检测的方法。此外,还探讨了活锁的情况及解决策略,并提到了可能导致线程饥饿的顺序加锁方式。最后,文章以哲学家进餐问题为例,进一步说明了并发问题的复杂性。
摘要由CSDN通过智能技术生成

锁的活跃性

1.死锁

1.1介绍

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。
t1线程获得A对象锁,接下来想获取B对象的锁, t2线程获得B对象锁,接下来想获取A对象的锁。

1.2死锁代码


static final Object A = new Object();
static final Object B = new Object();

@Test
public void testDeadLock() {
    //线程1获得A锁,又想获得B锁
    Thread t1 = new Thread(() -> {
        synchronized (A) {
            log.debug("获得A锁...");
            synchronized (B) {
                log.debug("获得B锁...");
            }
        }
    }, "t1");
    t1.start();
    //线程2获得B锁,又想获得A锁
    Thread t2 = new Thread(() -> {
        synchronized (B) {
            log.debug("获得B锁...");
            synchronized (A) {
                log.debug("获得A锁...");
            }
        }
    }, "t2");
    t2.start();
    try {
        t1.join();
        t2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

1.3死锁定位

检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:

方法1:使用jps,jstack

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

方法2:使用jconsole

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

死锁总结

  • 避免死锁要注意加锁顺序
  • 另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查

经典死锁问题-哲学家进餐问题

在这里插入图片描述
有五位哲学家,围坐在圆桌旁。
他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
如果筷子被身边的人拿着,自己就得等待。

package com.concurrent.p4;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

/**
 * 哲学家就餐问题
 */
public class TestQuestionPhilosopher {

    @Test
    public void test1() throws InterruptedException {
        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();

        while (true) ;
    }
}

@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
    //左筷子
    private Chopstick left;
    //右筷子
    private 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();
                }
            }
        }
    }

    public void eat() {
        log.debug("eat...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 筷子类
 */
class Chopstick {
    private String name;

    public Chopstick(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}

在这里插入图片描述

jconsole分析:

在这里插入图片描述
在这里插入图片描述

-------------------------------------------------------------------------
名称: 阿基米德
状态: cn.itcast.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.itcast.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.itcast.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.itcast.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.itcast.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
- 已锁定 cn.itcast.Chopstick@7f31245a (筷子4) 

2.活锁

活锁出现在两个线程虽然谁都没占用对方的锁,但是互相改变对方的结束条件,最后谁也无法结束。

static volatile int count = 10;
static final Object lock = new Object();

@Test
public void t1() {
    //期望减到0退出循环
    new Thread(() -> {
        while (count > 0) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count--;
            log.debug("count={}", count);
        }
    }, "t1").start();
    //期望加到20退出循环
    new Thread(() -> {
        while (count < 20) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            log.debug("count={}", count);
        }
    }, "t2").start();

    while (true) ;
}

在这里插入图片描述
解决活锁:
设置不同的间隔时间,增加随机睡眠时间。

3.饥饿

很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题。
下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题。

在这里插入图片描述
可以采用顺序加锁的方式解决死锁,

在这里插入图片描述
但是采取顺序加锁的方式可能会导致线程饥饿,

@Test
public void test1() throws InterruptedException {
    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();
    new Philosopher("阿基米德", c1, c5).start();
    while (true) ;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值