作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦
千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者
前言
在前面的文章中,壹哥 多次给大家提到过"死锁"的概念,但有些小伙伴却对"死锁"不甚了了。所以今天壹哥再通过一篇文章,来给大家专门讲解死锁的内容,希望大家认真掌握哦。
------------------------------前戏已做完,精彩即开始----------------------------
全文大约【1800】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......
配套开源项目资料
Github: github.com/SunLtd/Lear...
Gitee: gitee.com/sunyiyi/Lea...
一. 死锁
1. 概念
Java中的死锁是指多个线程同时占用一些共享资源且彼此相互等待,从而导致所有的线程都被阻塞,不能继续执行程序的情况。这就好比在一个十字路口,没有交警也没有红绿灯指挥通行,所有的车辆都占据道路且互相等待对方让出路权,此时就很容易造成道路堵死,这其实就是道路的"死锁"。如下图所示:
2. 死锁案例
虽然我们现在已经知道了死锁的概念,但具体什么时候会产生死锁,壹哥 相信很多小伙伴肯定还是弄不不清楚。所以接下来壹哥就给大家设计一个会产生死锁的代码案例,如下所示:
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public class Demo21 {
// 定义2个锁定的对象
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() {
// 锁定对象1
synchronized (lock1) {
System.out.println("Method 1: 获取对象lock1的锁");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 锁定对象2
synchronized (lock2) {
System.out.println("Method 1: 获取对象lock2的锁");
}
}
}
public void method2() {
// 锁定对象2
synchronized (lock2) {
System.out.println("Method 2: 获取对象lock2的锁");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 锁定对象1
synchronized (lock1) {
System.out.println("Method 2: 获取对象lock1的锁");
}
}
}
public static void main(String[] args) {
final Demo21 example = new Demo21();
Thread thread1 = new Thread(new Runnable() {
public void run() {
example.method1();
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
example.method2();
}
});
//开启线程
thread1.start();
thread2.start();
}
}
在上面的案例中,壹哥定义了两个方法method1和method2,这两个方法分别占用了lock1和lock2两个锁,并且在执行过程中会互相等待对方的锁,从而形成了死锁。如果我们运行该程序,就会看到两个线程互相等待对方释放自己占用的锁,这最终会导致所有的线程都被阻塞。上述案例中死锁的产生原因,如下图所示:
根据上面的案例,你可以总结出会导致死锁的条件吗?我们继续往下看。
3. 产生条件
其实一个Java程序要想产生死锁,也并不是那么容易,只有同时满足以下条件才行:
- 互斥条件:多个线程需同时访问一个共享资源,但每次只能有一个线程访问该资源;
- 请求和保持条件:一个线程在持有一个资源的同时,还想请求另一个资源;
- 不可剥夺条件:已经分配的资源不能被其他线程剥夺;
- 循环(环路)等待条件:多个线程形成了一个循环等待资源的链路,例如线程A等待线程B释放自己所占用的资源,线程B等待线程C释放自己所占用的资源,而线程C又等待线程A释放自己所占用的资源。
只有同时满足了以上条件,程序中才会产生死锁。既然我们现在知道了死锁的产生条件,那又该怎么解决呢?
4. 解决办法
我们知道,当出现死锁时,所有的线程都会被阻塞,且不能再继续执行程序,所以我们必须解决死锁。一般情况下,我们可以通过以下方式来避免线程死锁:
- 避免使用多个锁;
- 尽可能减少同步代码块的长度;
- 尝试改变锁的获取顺序,避免线程之间形成循环等待;
- 使用定时锁,当等待时间超过一定的时间值后就自动释放锁。
以上就是打破死锁条件的解决办法,但是具体放到Java代码中又是怎么样的呢?接下来壹哥就把上面产生死锁的代码修改一下,解决死锁问题。
5. 案例优化
接下来壹哥就把上面产生死锁的案例优化一下,解决掉案例中的死锁,代码如下:
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public class Demo22 {
// 定义2个锁定的对象
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() {
//锁定对象
synchronized(lock1) {
System.out.println("Method 1: 获取对象锁lock 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2) {
System.out.println("Method 1: 获取对象锁lock 2");
}
}
}
public void method2() {
synchronized(lock1) {
System.out.println("Method 2: 获取对象锁lock 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2) {
System.out.println("Method 2: 获取对象锁lock 2");
}
}
}
public static void main(String[] args) {
final Demo22 demo = new Demo22();
//定义两个线程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
demo.method1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
demo.method2();
}
});
//开启线程
thread1.start();
thread2.start();
}
}
上面的这个案例与之前的案例代码几乎一样,但与之不同的是,本案例中的方法method1和method2,都是先占用lock1锁,再占用lock2锁,这样就避免了死锁的发生,因为这两个方法占用锁的顺序是一致的。所以我们在编写多线程代码时,需要特别注意线程死锁的问题,避免影响程序的正常执行。
------------------------------正片已结束,来根事后烟----------------------------
二. 结语
至此,壹哥就把Java中的死锁给大家讲解完毕了,现在你明白了吗?我们在面试时经常会有面试官考察死锁相关的内容,比如死锁是怎么产生的?如何避免死锁?所以今天的内容很重要,请各位一定要牢牢掌握哦。
另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。