🔒 死锁代码
下面提供的代码演示了死锁的情况。程序创建了两个线程,线程1和线程2,它们都试图以不同的顺序获取两个不同的资源,resource1和resource2。线程1首先获取resource1,然后等待resource2,而线程2首先获取resource2,然后等待resource1。
这会创建一个死锁场景,其中两个线程都在等待另一个线程释放它们需要继续的资源。程序将不会终止,直到死锁得到解决
public static void main(String[] args) {
//资源1
Object resource1 = new Object();
//资源2
Object resource2 = new Object();
// 创建线程1,占用资源1,等待资源2
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: 占用资源1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) {
System.out.println("Thread 1: 占用资源2");
}
}
});
// 创建线程2,占用资源2,等待资源1
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: 占用资源2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource1) {
System.out.println("Thread 2: 占用资源1");
}
}
});
// 启动线程1和线程2
thread1.start();
thread2.start();
// 等待线程1和线程2执行完毕
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 此时线程1和线程2都已经执行完毕,程序结束
}
✅ 执行结果
线程开始进入阻塞状态, 导致方法无法执行结束
📳 定位死锁
方案1:
1. 先用jps查看所有的java进程id
2. jstack + 进程id定位死锁
从这里已经看出, 两个线程的状态都是BLOCKED,表示它们当前被阻塞等待某个对象的锁定。
"Thread-0"正在等待锁定资源2,因为它已经占用了资源1,而"Thread-1"正在等待锁定资源1,因为它已经占用了资源2。这就是死锁发生的情况。
Found one Java-level deadlock的意思就是 发现一个Java级别的死锁
对应的线程分别为 Thread-1 和 Thread-0
并且在下方还会提示你代码中死锁的位置 (死锁.java:38)
方案2:
从jdk的安装路径中找到bin目录, 点击jconsole
切换到线程, 点击检测死锁
通过以上两种方法, 我们就能很轻松的定位死锁的信息~
🔓 解决死锁的方案
在下面代码中,我们使用了可重入锁 ReentrantLock 来替代 synchronized 关键字进行线程同步。通过调用 lock() 方法获取锁,再使用 unlock() 方法释放锁。并且使用 try-catch-finally 语句块确保线程在任何情况下都会释放资源。
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockSolution {
public static void main(String[] args) {
//资源1
Object resource1 = new Object();
//资源2
Object resource2 = new Object();
//创建可重入锁对象
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
// 创建线程1,占用资源1,等待资源2
Thread thread1 = new Thread(() -> {
try {
lock1.lock(); // 获取锁1
System.out.println("Thread 1: 占用资源1");
Thread.sleep(100);
lock2.lock(); // 获取锁2
System.out.println("Thread 1: 占用资源2");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock2.unlock(); // 释放锁2
lock1.unlock(); // 释放锁1
}
});
// 创建线程2,占用资源2,等待资源1
Thread thread2 = new Thread(() -> {
try {
lock2.lock(); // 获取锁2
System.out.println("Thread 2: 占用资源2");
Thread.sleep(100);
lock1.lock(); // 获取锁1
System.out.println("Thread 2: 占用资源1");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock(); // 释放锁1
lock2.unlock(); // 释放锁2
}
});
// 启动线程1和线程2
thread1.start();
thread2.start();
// 等待线程1和线程2执行完毕
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 此时线程1和线程2都已经执行完毕,程序结束
}
}
总的来说,ReentrantLock 提供了更高级的线程同步机制,并且具有更高的灵活性和性能,但是使用起来也更为复杂。而 synchronized 则是 Java 的基本语言特性,在简单情况下使用起来比较方便,但是在特定场景下可能会出现死锁等问题。
如果想详细了解ReentrantLock的使用可以参考文章 :
扫描下方公众号二维码 领取多线程面试题 👇 👇 👇