【深入操作系统】线程相关常见现象解释(一)

135 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情

基本概念介绍

  • 死锁:两个线程 t1 t2,两把锁 A B,t1持有A锁 t2持有B锁,此时t1想要再获得B锁 t2想要再获得A锁,但是它们都不释放自己所持有的锁,最终导致死锁。
  • 活锁:两个线程互相改变对方的结束条件,导致最后谁也没办法结束
  • 饥饿:多个线程中 有一个线程t,由于线程之间的冲突 导致t线程分的时间片低,导致t线程基本不运行,这种情况下t线程就处于饥饿

死锁-哲学家就餐问题

  • 哲学家就餐问题是操作系统中介绍死锁时的经典案例。 这个故事说的是,有五位哲学家 坐在圆桌旁,他们只做两件事,吃饭和思考,思考一会吃口饭 吃完饭继续思考。吃饭时需要两根筷子,桌子上共有5根筷子,每位哲学家左右手各有一根筷子,如果身边的筷子被拿完,自己就得等待。

条件补充

  • 我们规定 每个人都先拿自己左手边的筷子 再拿右手边的筷子。

问题矛盾点

  • 我们发现 如果按上述描述规则吃饭,势必会有一种情况 就是 每个人都拿到自己左手边的筷子 但还需要右手边的筷子,但是右手边的筷子已经被占用,导致死锁。

代码示例

public class TestDeadLock {
    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();
                }
            }
        }
    }

    Random random = new Random();
    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 + '}';
    }
}

结果:

  • 01:48:37.984 c.Philosopher [苏格拉底] - eating…
  • 01:48:37.984 c.Philosopher [亚里士多德] - eating…
  • 01:48:38.493 c.Philosopher [阿基米德] - eating…
  • 01:48:38.493 c.Philosopher [柏拉图] - eating…
  • 01:48:39.003 c.Philosopher [柏拉图] - eating…
  • 01:48:39.003 c.Philosopher [赫拉克利特] - eating…
  • 01:48:39.513 c.Philosopher [苏格拉底] - eating…
  • 01:48:39.513 c.Philosopher [赫拉克利特] - eating…

解释:

我们创建一个筷子类Chopstick,一个哲学家类Philosopher,创建了五个筷子类对象 五个哲学家类对象,哲学家每次先尝试获取左边的筷子 再尝试获取右边的筷子,可以看到结果 产生了死锁。

image.png

根据jstack的结果 我们可以看出,确实发生了死锁,原因是五个哲学家都需要自己右手边哲学家的筷子

两个线程 t1 t2,两把锁 A B,t1持有A锁 t2持有B锁,此时t1想要再获得B锁 t2想要再获得A锁,可以看出t1 t2线程都尝试获取对方的锁,但最终陷入死锁,代码没有往下执行,首先我们用jps命令找到 我们需要的java程序 也就是 TestDeadLock,然后我们用jstack命令 查看19008进程的线程详细信息。