- 什么是死锁
- 分析过程
- 发生死锁的原因
- 避免死锁
- ThreadLocal
什么是死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。这是一个最严重的BUG之一。
分析过程
1.一个线程一把锁
一个线程对一个锁加锁两次,如果是可重入锁,不会产生死锁,如果是不可重入锁也加不了两次,谈不上死锁。
2.两个线程两把锁
车钥匙锁家里,家里的钥匙锁车里。
复现这个现象
public class Demo05_DeadLock {
public static void main(String[] args) {
// 定义两个锁对象
Object locker1 = new Object();
Object locker2 = new Object();
// 线程1,先获取locker1 再获取 locker2
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " t1 申请locker1..");
synchronized (locker1) {
System.out.println(Thread.currentThread().getName() + " t1 获取到了locker1.");
// 模拟业务处理过程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取locker2
System.out.println(Thread.currentThread().getName() + " t1 申请locker2...");
synchronized (locker2) {
System.out.println(Thread.currentThread().getName() + " t1 获取到了两把锁资源");
}
}
});
// 启动t1
t1.start();
// 线程2,先获取locker2 再获取 locker1
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " t2 申请locker2..");
synchronized (locker2) {
System.out.println(Thread.currentThread().getName() + " t2 获取到了locker2.");
// 模拟业务处理过程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取locker1
System.out.println(Thread.currentThread().getName() + " t2 申请locker1...");
synchronized (locker1) {
System.out.println(Thread.currentThread().getName() + " t2 获取到了两把锁资源");
}
}
});
// 启动t2
t2.start();
}
}
发生死锁的原因
1.互斥使用:锁A被线程1占用了,线程2就不能用了;
2.不可抢占:锁A被线程1占用了,线程2不能主动把锁A抢过来,除非线程1主动释放;
3.请求保持:有多把锁,线程1拿到锁A之后不释放,还要继续再拿;
4.循环等待:线程1等待线程2释放锁,线程2要释放锁得等待线程3先释放锁,线程3释放锁得等待线程1释放锁,形成了循环关系。
🔴有这样一个经典的“哲学家吃面问题”
有个桌子, 围着一圈哲学家, 桌子中间放着一盘意大利面. 每个哲学家两两之间,放着一根筷子,每个哲学家只做两件事: 思考人生或者吃面条。思考人生的时候就会放下筷子。吃面条就会拿起左右两边的筷子(先拿起左边,再拿起右边)。如果哲学学家发现筷子拿不起来了(被别人占用了), 就会阻塞等待。
假设同一时刻, 五个哲学家同时拿起左手边的筷子,然后再尝试拿右手的筷子, 就会发现右手的筷子都被占用了。由于哲 学家们互不相让, 这个时候就形成了死锁。
避免死锁
以上四条是死锁产生的必要条件。在死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
1.互斥使用:这个不能被打破,这是锁的基本特性;
2.不可抢占:这个也不能被打破,这也是锁的基本特性;
3.请求保持:这个可以被打破,取决于代码怎么写;
4.循环等待:约定好加锁顺序就可以打破循环等待。
👀以哲学家就餐问题为例,不像以前那样先拿左手的筷子再拿右手的筷子,重新安排一下拿筷子的顺序。
1.让每个哲学家必须先去拿编号小的筷子;
2.拿到后再去拿编号大的筷子;
3.如果五个哲学家同时去拿筷子的话那么5和1都会先抢筷子1;
4.筷子1只有一个哲学家会先拿到,抢不到的那个哲学家就要等;
5.哲学家4就会获得到两只筷子,吃完面后,其他的可以获取的筷子的哲学家继续吃;
6.最终其他哲学家吃完之后,5号再获取到筷子完成吃面。
在复现的例子中,t1.locker1——>locker2,t2.locker2——>locker1,调整加锁顺序,就可以避免循环等待。
public class Demo05_DeadLock {
public static void main(String[] args) {
// 定义两个锁对象
Object locker1 = new Object();
Object locker2 = new Object();
// 线程1,先获取locker1 再获取 locker2
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " t1 申请locker1..");
synchronized (locker1) {
System.out.println(Thread.currentThread().getName() + " t1 获取到了locker1.");
// 模拟业务处理过程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取locker2
System.out.println(Thread.currentThread().getName() + " t1 申请locker2...");
synchronized (locker2) {
System.out.println(Thread.currentThread().getName() + " t1 获取到了两把锁资源");
}
}
});
// 启动t1
t1.start();
// 线程2,先获取locker2 再获取 locker1
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " t2 申请locker1..");
synchronized (locker1) {
System.out.println(Thread.currentThread().getName() + " t2 获取到了locker1.");
// 模拟业务处理过程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取locker1
System.out.println(Thread.currentThread().getName() + " t2 申请locker2...");
synchronized (locker2) {
System.out.println(Thread.currentThread().getName() + " t2 获取到了两把锁资源");
}
}
});
// 启动t2
t2.start();
}
}
ThreadLocal
有这样一个场景,多个班级,每个班级要统计一下班里人数,根据人数去做校服。这时可以使用ThreadLocal,不是让所有线程修改同一个共享变量,而是让每个线程修改各自的变量。
public class Demo06_ThreadLocal {
// 初始化一个ThreadLocal
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 多个线程分别去统计人数
Thread thread1 = new Thread(() -> {
// 统计人数
int count = 35;
threadLocal.set(count);
// Integer value = threadLocal.get();
// System.out.println(value);
// 订制校服
print();
}, "threadNameClass1");
Thread thread2 = new Thread(() -> {
// 统计人数
int count = 40;
threadLocal.set(count);
// Integer value = threadLocal.get();
// System.out.println(value);
// 订制校服
print();
}, "threadNameClass2");
thread1.start();
thread2.start();
}
// 订制校服
public static void print() {
// 从threadLocal中获取值
Integer value = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " : 需要订制 " + value + "套校服.");
}
}
set()方法:
继续加油~