并发Liveness问题
About 3 min
多个线程协同工作,总是会遇到一些莫名其妙的情况。有可能出现多个线程敌不动我不动的情形,都在等待对方线程做些什么。有的线程不积极抢不过其他线程,可能一直没机会得到调度。也有可能多个线程抢占某个资源,势均力敌互不相让,结果一直在拉扯不休。这些问题统称为并发的Liveness
问题,下面我们就无情揭开它们的面纱。
死锁Deadlock
死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,而又相互等对方释放锁,此时若无外力干预,这些线程则一直处理阻塞的假死状态,形成死锁。
死锁的例子
A同学抢了B同学的钢笔,B同学抢了A同学的书,两个人都相互占用对方的东西,都在让对方先还给自己自己再还,这样一直争执下去等待对方还而又得不到解决。
public class DeadLock {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
var thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " get lock1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " need lock2");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " get lock2");
}
}
});
var thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " get lock2");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " need lock1");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " get lock1");
}
}
});
thread1.start();
thread2.start();
}
}
输出结果:
Thread-1 get lock2
Thread-0 get lock1
Thread-0 need lock2
Thread-1 need lock1
死锁的排查
- 执行上述的程序,然后使用
jps -l
64016 com.huhx.thread.liveness.DeadLock
64017 org.jetbrains.jps.cmdline.Launcher
2392
64460 jdk.jcmd/sun.tools.jps.Jps
88127
- 然后使用
jstack 64016
,然后可以看到包含如下的输出信息
Java stack information for the threads listed above:
===================================================
"Thread-0":
at com.huhx.thread.liveness.DeadLock.lambda$main$0(DeadLock.java:20)
- waiting to lock <0x000000070ffed740> (a java.lang.Object)
- locked <0x000000070ffed730> (a java.lang.Object)
at com.huhx.thread.liveness.DeadLock$$Lambda$14/0x0000000800c031f0.run(Unknown Source)
at java.lang.Thread.run(java.base@18.0.2.1/Thread.java:833)
"Thread-1":
at com.huhx.thread.liveness.DeadLock.lambda$main$1(DeadLock.java:35)
- waiting to lock <0x000000070ffed730> (a java.lang.Object)
- locked <0x000000070ffed740> (a java.lang.Object)
at com.huhx.thread.liveness.DeadLock$$Lambda$15/0x0000000800c033f8.run(Unknown Source)
at java.lang.Thread.run(java.base@18.0.2.1/Thread.java:833)
Found 1 deadlock.
从上述打印信息我们知道:
饥饿Starvation
我们知道多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿。当然还有一种饥饿的情况,一个线程一直占着一个资源不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够得到执行的,如那个占用资源的线程结束了并释放了资源
饥饿的例子
public class StarvationLock {
private static final Object lock = new Object();
public static void main(String[] args) {
var thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " owned lock");
while (true) {
}
}
});
var thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " get lock");
}
});
thread1.start();
thread2.start();
}
}
输出结果:
Thread-0 owned lock
活锁Livelock
活锁恰恰与死锁相反,死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。当多线程中出现了相互谦让,都主动将资源释放给别的线程使用,这样这个资源在多个线程之间跳动而又得不到执行,这就是活锁
活锁的例子
两个迎面走来的两个绅士互相礼让,A想让B通过往右边礼让,同时B想让A通过也往右礼让。此时AB都在右边,然后两人又同时开始礼让对方,于是两人又都在左边。于是两开始了无限次的礼让,但是仍旧没有一个人能够通过。这就是活锁