线程死锁
线程死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象。
线程死锁的产生
线程死锁产生必须具备四个条件:
-
互斥条件:指线程对己经获取到的资源进行它性使用即该资源同时只由个线程占用。如果此时还有其线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
-
请求并持有条件:指一个线程己经持有了至少一个资源但又提出了新的资源请求而新资源己被其线程占有,所 以当前线程会被阻塞,但阻塞的同时并不释放自己经获取的资源。
-
不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其线程抢占,只有在自己使用完后才释放该资源。
-
环路等待条件:指在发生死锁时,必然存在一个(线程---资源)的环形链,即线程集合{T1,T2,T3 ----- Tn}中的T1正在等待T2的占用的资源,T2正在等待T3的占用资源... ...Tn正在等待T1的占用资源。
死锁代码演示:
package com.thread.study;
public class DeadLockThread{
private final Object read_lock = new Object(); //读取锁
private final Object write_lock = new Object(); //写入锁
public void read() {
synchronized (read_lock) {
System.out.println(Thread.currentThread().getName().concat("获取读取锁"));
synchronized (write_lock) {
System.out.println(Thread.currentThread().getName().concat("获取写锁"));
}
System.out.println(Thread.currentThread().getName().concat("释放写锁"));
}
System.out.println(Thread.currentThread().getName().concat("释放读取锁"));
}
public void write() {
synchronized (write_lock) {
System.out.println(Thread.currentThread().getName().concat("获取写锁"));
synchronized (read_lock) {
System.out.println(Thread.currentThread().getName().concat("获取读取锁"));
}
System.out.println(Thread.currentThread().getName().concat("释放读取锁"));
}
System.out.println(Thread.currentThread().getName().concat("释放写锁"));
}
public static void main(String[] args) {
DeadLockThread deadLockThread = new DeadLockThread();
Thread thread1 = new Thread(deadLockThread::read);
Thread thread2 = new Thread(deadLockThread::write);
thread1.start();
thread2.start();
}
}
操作多线程的时候一定要注意避免死锁的情况发生,死锁会导致系统资源的大量占用,导致系统性能变慢甚至卡死。
如何避免线程死锁
要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可。
常用解决死锁方法是摒弃环路等待条件:申请资源的顺序调整,使用资源申请的有序性原则就可以避免死锁。
检测死锁
通过jdk提供的工具jconsole排查死锁问题
jconsole.exe :jdk提供的一个可视化的工具,方便排查程序的一些问题,如:程序内存溢出、死锁问题等等。jconsole.exe位于jdk的bin目录中.
jconsole.exe使用示例:
消除死锁的几种方式
- 进行系统的重新启动;
- 撤消进程,剥夺资源。终止参与死锁的进程,收回它们占有的资源,从而解除死锁。这时又分两种情况:一次性撤消参与死锁的全部进程,剥夺全部资源;或者逐步撤消参与死锁的进程,逐步收回死锁进程占有的资源。
- 进程回退策略,即让参与死锁的进程回退到没有发生死锁前某一点处,并由此点处继续执行,以求再次执行时不再发生死锁。虽然这是个较理想的办法,但是操作起来系统开销极大,要有堆栈这样的机构记录进程的每一步变化,以便今后的回退,有时这是无法做到的。
实际工作中避免死锁的方式
1、避免一个线程同时获取多个锁。
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3、尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
4、在高并发业务中,确保在峰值并发时有足够大的资源池。
5、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。比如,在循环执行中,每个循环逻辑使用不同的数据库连接,而不会导致嵌套死锁。
6、使用池化思想
网友评论