十二、多线程基础-死锁
2021-07-03 11:07
标签:自己 严格 计算机系 code 区别 string 生命周期 run 表示 1、死锁含义 方法2:通过Lock接口实现:首先获得锁的线程1执行完毕后没有释放锁导致线程2无法获取锁。 7、避免死锁程序示例(2种方法) 7.2、设置加锁时限 (线程尝试获取锁的时候加上一定的时限, 超过时限则放弃对该锁的请求, 并释放自己占有的锁) 十二、多线程基础-死锁 标签:自己 严格 计算机系 code 区别 string 生命周期 run 表示 原文地址:https://www.cnblogs.com/jiarui-zjb/p/9623100.html
死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。例如,在某一个计算机系统中只有一台打印机和一台输入 设备,进程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 相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
*/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();
}
}
/*
Lock接口的主要方法:
lock():获取锁,如果锁被暂用则一直等待
unlock():释放锁
tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true
tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间
lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事
*/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();
}
}
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();这样就可以避免死锁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();
}
}
/*
设置加锁时限:项目功能简介
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做同样的处理。
*/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();
}
}