目录
1. 问题引入
在学习死锁之前, 我们先观察下面的代码能否输出正确的结果:
运行程序, 能正常输出结果:
这个代码只管上看起来, 好像是有锁冲突的, 此时的 locker 对象已经是加锁的状态, 在尝试对 locker 加锁, 不应该会出现阻塞问题吗?
其实, 问题的关键是,这两次加锁, 其实是在同一个线程上进行的. 由于是同一个线程, 此时锁对象就知道了第二次加锁的线程, 第二次加锁操作就可以直接放行通过, 不会出现阻塞. 这个特性称为 "可重入".
使用可重入锁, 可以避免代码出现死锁问题, 如果使用的不是可重入锁, 就会出现死锁问题.
2.死锁问题的概念和原因
通常,发生死锁问题需要满足以下四个条件:
死锁的例子:
线程1获取到锁A, 线程2获取到锁B, 接下来, 线程1尝试获取锁 B, 线程2尝试获取锁A, 此时出现了死锁问题:
package thread;
public class ThreadDemo22 {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()-> {
synchronized (A) {
// sleep 是为了t2时间, 让t2也能拿到 B
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
//尝试获取B, 并没有释放 A
synchronized (B) {
System.out.println("t1拿到了两把锁");
}
}
});
Thread t2 = new Thread(()->{
synchronized (B) {
// sleep 是给t1时间, 让t1能拿到 A
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
//尝试获取A, 并没有释放 B
synchronized (A) {
System.out.println("t2拿到了两把锁");
}
}
});
t1.start();
t2.start();
}
}
程序没有任何输出结果:
3. 解决死锁问题
为了避免死锁问题,可以采取以下策略:
对于死锁例子, 我们可以使两个线程的取锁顺序保持一致:
package thread;
public class ThreadDemo22 {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(()-> {
synchronized (A) {
// sleep 是为了t2时间, 让t2也能拿到 B
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
//尝试获取B, 并没有释放 A
synchronized (B) {
System.out.println("t1拿到了两把锁");
}
}
});
Thread t2 = new Thread(()->{
synchronized (A) {
// sleep 是给t1时间, 让t1能拿到 A
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
//尝试获取A, 并没有释放 B
synchronized (B) {
System.out.println("t2拿到了两把锁");
}
}
});
t1.start();
t2.start();
}
}