活跃性问题概述
- 线程没有按照预期结束或执行不下去的情况,属于活跃性问题
- 活跃性问题包括死锁、活锁、饥饿三种情况
解决活跃性问题
Java通过使用ReentrantLock解决三种活跃性问题
1 死锁
- 多把锁概述
- 有些场景使用一把锁时会导致并发度降低,从而导致性能的下降,考虑以下场景
- 一间大屋子有两个功能:睡觉、学习,两者互不相干。现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低,解决方法是准备多个房间(多个对象锁)
- 对于没有关联的业务可以给予不同的锁(如睡觉和学习),以增强并发度
但是多把锁的坏处是如果一个线程需要同时获得多把锁,容易发生死锁
死锁概述
- 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
- 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁实例
t1线程获得锁对象A,接下来想获取锁对象B;t2线程获得锁对象B,接下来想获取锁对象A,此时就发生了死锁
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
- 定位/检测死锁
- 如果发生死锁,首先需要定位死锁,然后才能尝试解决死锁
- 定位死锁可以使用jconsole工具
2 活锁
活锁概述
- 活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复 尝试—失败—尝试—失败的过程。
- 处于活锁的实体是在不断的改变状态,例如两个线程互相改变对方的结束条件
- 活锁有可能自行解开
活锁实例 ```java static volatile int count = 10; static final Object lock = new Object();
public static void main(String[] args) { new Thread(() -> { while (count > 0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } count—; log.debug(“count: {}”, count); } }, “t1”).start(); new Thread(() -> { while (count < 20) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } count++; log.debug(“count: {}”, count); } }, “t2”).start(); } ```
3 饥饿
- 饥饿概述
饥饿 ,与死锁和活锁非常相似。是指一个可运行的进程尽管能继续执行,但被调度器无限期地忽视,而不能被调度执行的情况。