在多线程的世界里,一个不小心可能就会造成”死锁“,一旦”死锁“了意味着CPU会一直在空转,结果就是CPU一下子飙到100%,所以避免死锁的前提,就要去聊聊什么是”死锁“以及如何去避免死锁。
什么是死锁?
死锁指的是,两个或者多个线程,持有了彼此请求的资源,导致线程之间互相在等待对方释放资源,所以造成了死锁,下面我们来手撸一个简单的死锁例子。
public class DeadLock {
public static void main(String[] args) {
Object lock1 = new Object();
n
Object lock2 = new Object();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1) {
System.out.println("thread get lock1");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("thread get lock2");
}
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock2) {
System.out.println("thread1 get lock2");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("thread1 get lock1");
}
}
}
});
thread.start();
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread's status:"+thread.getState());
System.out.println("thread1's status:"+thread1.getState());
}
}
打印的结果如下
可以看到线程thread
先拿到了锁lock1
,然后工作了一会之后,想要获取锁lock2
,但是此时锁lock2
被线程thread1
占有着,而线程thread1
同时也在请求获取被线程thread
占有的锁lock
,所以从打印信息可以看出,两个线程都处于阻塞等待状态。
造成死锁的必要条件
造成死锁必须满足四个必要条件,如下表所示。
条件 | 描述 |
---|---|
资源互斥性 | 被共享的同步资源每次只能被一个线程占有 |
资源不可抢占性 | 资源请求方不可以抢占资源拥有方占有的资源 |
请求和占有 | 资源请求方在请求资源的同时,也占有着原来拥有的资源 |
循环等待 | 多个线程之间相互等待对方释放资源,造成循环等待 |
检测死锁的方法
一般遇到死锁,时间久了,CPU可能会飙到100%,所以可以通过top命令查看cpu过高的进程,或者直接通过命令
jps
查看java进程id号,然后通过命令jstack - id
来查看进程的堆栈情况,搜索一下DeadLock
关键字就可以确定死锁发生的位置。
死锁的避免
只要破坏死锁的四个必要条件中的一个,就可以避免死锁了。对于”资源互斥性“条件是很难被破坏的,因为资源互斥是保证线程安全的前提条件。
使用Lock来破坏“请求和占有”和“资源不可抢占”条件
可以使用JUC包下的ReentrantLock类来作为锁对象,这样线程在等待指定时间后,还未获取到锁对象,就会返回失败状态,然后在finally中主动去释放掉自己占有的锁对象,这样就破坏了“请求和占有”和“资源不可抢占”两个条件了,代码如下。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: tengxun
* @description: 死锁
* @author: cjr
* @create: 2020-06-29 22:00
**/
public class DeadLock {
public static void main(String[] args) {
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
if (lock1.tryLock(2000, TimeUnit.MICROSECONDS)) {
System.out.println("thread get lock1");
Thread.sleep(50);
if (lock2.tryLock(2000,TimeUnit.MICROSECONDS)) {
System.out.println("thread get lock2");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
if (lock2.tryLock(2000, TimeUnit.MICROSECONDS)) {
System.out.println("thread1 get lock2");
Thread.sleep(50);
if (lock1.tryLock(2000,TimeUnit.MICROSECONDS)) {
System.out.println("thread1 get lock1");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread's status:"+thread.getState());
System.out.println("thread1's status:"+thread1.getState());
}
}
输出的结果如下
规定好获取锁的顺序,这样就可以破坏"循环等待"条件
之所以会循环等待,就是因为获取锁的顺序,线程A先获取锁对象1,然后获取锁对象2,线程B先获取锁对象2,然后获取锁对象1,所以造成循环等待对方释放锁资源,造成死锁,我们可以根据对象的hashcode的大小来决定获取锁的顺序,代码如下
public class DeadLock {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Object left = null;
Object right = null;
left = lock1.hashCode() > lock2.hashCode() ? lock1 : lock2;
right = lock1.hashCode() > lock2.hashCode() ? lock2 : lock1;
synchronized (left) {
System.out.println("thread get lock1");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (right) {
System.out.println("thread get lock2");
}
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Object left = null;
Object right = null;
left = lock1.hashCode() > lock2.hashCode() ? lock1 : lock2;
right = lock1.hashCode() > lock2.hashCode() ? lock2 : lock1;
synchronized (left) {
System.out.println("thread1 get lock2");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (right) {
System.out.println("thread1 get lock1");
}
}
}
});
thread.start();
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread's status:"+thread.getState());
System.out.println("thread1's status:"+thread1.getState());
}
}
输出结果如下