1. 线程活跃性分析
1.1. 死锁
1.1.1. 定义
两个或两个以上的线程在执行过程中,这些线程竞争资源,在某一时刻所有的线程都在互相等待自己所需要的资源但永远得不到造成无限制等待下去,这就是死锁现象。
1.1.2. 例子
@Slf4j(topic = "c.Test-deadLock")
public class Test27 {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
Thread t1 = new Thread(() -> {
synchronized (a){
log.debug("拿到锁a,要继续获得锁b");
Sleeper.sleep(1);
synchronized (b){
log.debug("拿到锁b");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (b){
log.debug("拿到锁b,要继续获得锁a");
Sleeper.sleep(1);
synchronized (a){
log.debug("拿到锁a");
}
}
}, "t2");
t1.start();
t2.start();
}
}
1.1.3. 哲学家就餐问题(Dining Philosopher Problem)
package com.huangbei.test;
import com.huangbei.util.Sleeper;
import lombok.extern.slf4j.Slf4j;
/*
哲学家就餐问题:5个人围坐一个圆桌,每个人的左手边和左手边都有1根筷子,总共5根筷子。
每个人吃饭需要两根筷子,吃完饭就把筷子放下休息一会儿,然后继续吃饭……,这个场景就会引起死锁问题。
*/
@Slf4j(topic = "c.DiningPhilosopherProblem")
public class Test28 {
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 {
//left和right分别代表左手边和右手边的筷子
private Chopstick left;
private Chopstick right;
Philosopher(String name, Chopstick l, Chopstick r) {
super(name);
left = l;
right = r;
}
private void eat() {
log.debug("吃饭.");
Sleeper.sleep(1);
}
@Override
public void run() {
while (true) {
synchronized (left) {
synchronized (right) {
eat();
}
}
}
}
}
class Chopstick {
String name;
Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子" + name;
}
}
1.2. 活锁
1.2.1. 定义
线程没有阻塞,但它们之间互相改变对方的结束条件,导致线程一直在执行。
百度百科定义:活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
1.2.2. 例子
package com.huangbei.test;
import com.huangbei.util.Sleeper;
import lombok.extern.slf4j.Slf4j;
/*
活锁
*/
@Slf4j(topic = "c.Test-liveLock")
public class Test29 {
static int count = 10;
static final Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (count > 0) {
synchronized (obj){
Sleeper.sleep(0.2);
count--;
log.debug("count={}", count);
}
}
}, "t1").start();
new Thread(() -> {
while (count < 20) {
synchronized (obj){
Sleeper.sleep(0.2);
count++;
log.debug("count={}", count);
}
}
}, "t2").start();
}
}
1.3. 饥饿
1.3.1. 定义
如果一个线程没有被分配到 CPU 执行时间,该线程就处于“饥饿”状态。如果总是分配不到 CPU 执行时间(因为总是被分配到其他线程去了),那么该线程可能会被“饿死”。有一种策略用于避免出现该问题,称作“公平策略”,即保证所有的线程都能公平地得到被执行的机会。
1.3.2. 原因
在 Java 中,有三种最普遍的情形会导致饥饿的发生:
- 高优先级的线程总是吞占 CPU 执行时间,导致低优先级的线程没有机会;
- 某些线程总是能被允许进入 synchronized 块,以致某些线程总是得不到机会;
- 某些线程在等待指定的对象(即调用了该对象的 wait() 方法)时,完全得不到唤醒的机会,因为被唤醒的总是别的线程。