JUC并发编程 活跃性 -- 活锁 & 死锁/活锁的区别 & 饥饿

Posted Z && Y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程 活跃性 -- 活锁 & 死锁/活锁的区别 & 饥饿相关的知识,希望对你有一定的参考价值。

1. 活锁

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

package tian;

import lombok.extern.slf4j.Slf4j;

@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) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        new Thread(() -> {
            // 期望超过 20 退出循环
            while (count < 20) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

运行结果:


2. 死锁/活锁的区别

死锁:

2个线程互相持有对方想要的锁,导致无法继续向下运行( 2个线程都是阻塞状态 )

活锁:

2个线程都在继续运行,但是由于改变了对方的结束条件,导致2个线程结束不了


3. 饥饿

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

import lombok.extern.slf4j.Slf4j;

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("阿基米德", c1, c5).start();
    }
}

@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
    final Chopstick left;
    final 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...");
    }
}

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

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

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

运行结果:



以上是关于JUC并发编程 活跃性 -- 活锁 & 死锁/活锁的区别 & 饥饿的主要内容,如果未能解决你的问题,请参考以下文章

JUC并发编程 活跃性 -- 死锁 & 定位死锁 & 哲学家就餐问题

高并发编程-05-活跃性问题

高级程序员需知的并发编程知识

JUC并发编程 -- JUC介绍 & 线程/进程 & 并发/并行 & Java代码查看CPU的核数

并发编程-阻塞队列&JUC常用工具

活跃性(死锁饥饿活锁)