1、死锁含义
死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。例如,在某一个计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
2、死锁产生原因
1)系统资源的竞争:系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。
2)进程运行推进顺序不合适:进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。
3、产生死锁的四个必要条件
1)互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2)请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
3)不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
4)循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
4、避免死锁的基本思想
系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何让这四个必要条件不成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
5、死锁避免和死锁预防的区别
死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁的出现,而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。
6、形成死锁的程序示例(2种方法)
方法1:通过synchronized实现
/*
一个简单的死锁类:
当 DeadLock 类的对象 flag==1 时(td1),先锁定 o1,睡眠 500 毫秒
而 td1 在睡眠的时候另一个 flag==0 的对象(td2)线程启动,先锁定 o2,睡眠 500 毫秒
td1 睡眠结束后需要锁定 o2 才能继续执行,而此时 o2 已被 td2 锁定;
td2 睡眠结束后需要锁定 o1 才能继续执行,而此时 o1 已被 td1 锁定;
td1、td2 相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
*/
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public class DeadLock implements Runnable { public int flag = 1; // 静态对象是类的所有对象共享的 private static Object o1 = new Object(), o2 = new Object(); public void run() { if (flag == 1) { System.out.println(Thread.currentThread().getName()+"flag=" + flag); synchronized (o1) { try { //sleep(100)这样可以防止Thread1在启动时,一下子就获取到了o1和o2锁 Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o2) { System.out.println("同时持有o1和o2对象锁"); } } } if (flag == 0) { System.out.println(Thread.currentThread().getName()+"flag=" + flag); synchronized (o2) { try { //sleep(100)这样可以防止Thread2在启动时,一下子就获取到了o1和o2锁 Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o1) { System.out.println("同时持有o2和o1对象锁"); } } } } public static void main(String[] args) { DeadLock td1 = new DeadLock(); DeadLock td2 = new DeadLock(); td1.flag = 1; td2.flag = 0; // td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。 // td2 的 run()可能在 td1 的 run()之前运行 Thread thread1=new Thread(td1); Thread thread2=new Thread(td2); thread1.setName("Thread1"); thread2.setName("Thread2"); thread1.start(); thread2.start(); } }
方法2:通过Lock接口实现:首先获得锁的线程1执行完毕后没有释放锁导致线程2无法获取锁。
/*
Lock接口的主要方法:
lock():获取锁,如果锁被暂用则一直等待
unlock():释放锁
tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true
tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间
lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事
*/
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest1 { /* ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义, 但功能更强大。ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时, 调用 lock() 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回 */ private Lock lock = new ReentrantLock(); // 需要参与同步的方法 private void method(Thread thread) { lock.lock(); try { System.out.println("线程名" + thread.getName() + "获得了锁"); } catch (Exception e) { e.printStackTrace(); } /*finally { lock.unlock(); System.out.println("线程名" + thread.getName() + "释放了锁"); }*/ } public static void main(String[] args) { final LockTest1 lockTest = new LockTest1(); // 线程1 Thread t1 = new Thread(new Runnable() { public void run() { lockTest.method(Thread.currentThread()); } }, "t1"); Thread t2 = new Thread(new Runnable() { public void run() { lockTest.method(Thread.currentThread()); } }, "t2"); t1.start(); t2.start(); } }
7、避免死锁程序示例(2种方法)
1)加锁顺序(线程按照一定的顺序加锁)
2)设置加锁时限-->参看AvoidDeadLock2
7.1、设置加锁顺序:项目功能简介
flag=1的t1线程 ,执行时先锁定 o1,睡眠 500 毫秒
在t1线程执行时 flag=2的t2线程也开始执行,执行时先先锁定 o2,睡眠 500 毫秒
t1睡眠结束后需要锁定 o2 才能继续执行,而此时 o2 已被 td2 锁定;
t2睡眠结束后需要锁定 o1 才能继续执行,而此时 o1 已被 td1 锁定;t1和t2都相互等待,都需要得到对方锁定的资源才能继续进行,从而死锁。
如何避免死锁方法1(设置设置加锁顺序):应该让t2在开始执行前先等待t1执行完毕再执行 也就是t1.join();这样就可以避免死锁
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public class AvoidDeadLock1 { public int flag = 1; // 静态对象是类的所有对象共享的 private static Object o1 = new Object(), o2 = new Object(); public void money(int flag) { this.flag = flag; if (flag == 1) { synchronized (o1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o2) { System.out.println("当前的线程是" + Thread.currentThread().getName() + " " + "flag 的值" + "1"); } } } if (flag == 0) { synchronized (o2) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o1) { System.out.println("当前的线程是" + Thread.currentThread().getName() + " " + "flag 的值" + "0"); } } } } /* 1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。 2)finally:异常处理语句结构的一部分,表示总是执行。 3)finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法 提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法 被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对 象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用 */ public static void main(String[] args) { final AvoidDeadLock1 td1 = new AvoidDeadLock1(); final AvoidDeadLock1 td2 = new AvoidDeadLock1(); td1.flag = 1; td2.flag = 0; // td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。 // td2 的 run()可能在 td1 的 run()之前运行 final Thread t1 = new Thread(new Runnable() { public void run() { td1.flag = 1; td1.money(1); } }); t1.setName("线程t1"); t1.start(); Thread t2 = new Thread(new Runnable() { public void run() { // TODO Auto-generated method stub try { // 让 t2 等待 t1 执行完 t1.join();// 核心代码,让 t1 执行完后 t2 才会执行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } td2.flag = 0; td2.money(0); } }); t2.setName("线程t2"); t2.start(); } }
7.2、设置加锁时限 (线程尝试获取锁的时候加上一定的时限, 超过时限则放弃对该锁的请求, 并释放自己占有的锁)
/*
设置加锁时限:项目功能简介
flag=1的t1线程 ,执行时先锁定 o1,睡眠 500 毫秒
在t1线程执行时 flag=2的t2线程也开始执行,执行时先先锁定 o2,睡眠 500 毫秒
t1睡眠结束后需要锁定 o2 才能继续执行,而此时 o2 已被 td2 锁定;
t2睡眠结束后需要锁定 o1 才能继续执行,而此时 o1 已被 td1 锁定;t1和t2都相互等待,都需要得到对方锁定的资源才能继续进行,从而死锁。
如何避免死锁方法2(设置加锁时限):应该让t1先获取到锁,如果获取不到就等待5秒,如果5秒后还是获取不到就返回false。获取到锁后就开始执行,
执行完后释放此锁。t2做同样的处理。
*/
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class AvoidDeadLock2 { public int flag = 1; // 静态对象是类的所有对象共享的 private static Object o1 = new Object(), o2 = new Object(); public void money(int flag) throws InterruptedException { this.flag = flag; if (flag == 1) { synchronized (o1) { Thread.sleep(500); synchronized (o2) { System.out.println("当前的线程是" + Thread.currentThread().getName() + " " + "flag 的值" + "1"); } } } if (flag == 0) { synchronized (o2) { Thread.sleep(500); synchronized (o1) { System.out.println("当前的线程是" + Thread.currentThread().getName() + " " + "flag 的值" + "0"); } } } } public static void main(String[] args) { /* * ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized * 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 ReentrantLock * 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。 * 如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 * getHoldCount()方法来检查此情况是否发生。 */ final Lock lock = new ReentrantLock(); final AvoidDeadLock2 td1 = new AvoidDeadLock2(); final AvoidDeadLock2 td2 = new AvoidDeadLock2(); td1.flag = 1; td2.flag = 0; // td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。 // td2 的 run()可能在 td1 的 run()之前运行 final Thread t1 = new Thread(new Runnable() { public void run() { // TODO Auto-generated method stub String tName = Thread.currentThread().getName(); td1.flag = 1; try { // 获取不到锁,就等 5 秒,如果 5 秒后还是获取不到就返回 false if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) { System.out.println(tName + "获取到锁!"); } else { System.out.println(tName + "获取不到锁!"); return; } } catch (Exception e) { e.printStackTrace(); } try { td1.money(1); } catch (Exception e) { System.out.println(tName + "出错了!!!"); } finally { lock.unlock(); System.out.println("当前的线程是" + Thread.currentThread().getName() + "释放锁!!"); } } }); t1.start(); Thread t2 = new Thread(new Runnable() { public void run() { String tName = Thread.currentThread().getName(); // TODO Auto-generated method stub td1.flag = 1; try { // 获取不到锁,就等 5 秒,如果 5 秒后还是获取不到就返回 false if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) { System.out.println(tName + "获取到锁!"); } else { System.out.println(tName + "获取不到锁!"); return; } } catch (Exception e) { e.printStackTrace(); } try { td2.money(0); } catch (Exception e) { System.out.println(tName + "出错了!!!"); } finally { lock.unlock(); System.out.println("当前的线程是" + Thread.currentThread().getName() + "释放锁!!"); } } }); t2.start(); } }