目录
死锁
死锁是指两个或者两个以上的线程在执行过程中,由于竞争资源而造成的阻塞问题,若无外力作用下,他们将无法推进下去。此时系统处于死锁状态
死锁产生原因:
1、因竞争资源产生死锁
2、进程顺序推进不当发生死锁
出现死锁的必要条件:
1、互斥条件:资源每次只能是一个线程使用-》资源
2、请求与保持条件:一个线程因请求资源而阻塞时,对以获取的资源保持不放-》线程
3、不可剥夺条件:线程已获取的资源,在未使用之前,不能强行剥夺
4、循环等待条件:若干线程之间形成一种头尾相连接的循环等待资源关系。
死锁的预防或解除
解决死锁的途径:预防、避免、检测和恢复
1、预防死锁(破坏4个必要条件)
a、资源一次性分配(破坏请求与保持条件)
b、可剥夺资源:在线程未满足条件时,释放掉已占有的资源
c、资源有序分配:系统给每类资源赋予一个编号,每个线程按编号递增的顺序请求资源,释放资源顺序相反;
2、避免死锁(银行家算法)
允许线程动态地申请资源。系统在资源分配之前先计算资源分配的安全性,若此次分配不会导致系统进入不安全状态,则给线程分配该资源,否则进程等待。
银行家算法:
共有四种资源,空格隔开;
线程名称 | 已拥有的资源 | 还需要的资源 |
---|---|---|
p0 | 0 0 3 2 | 0 0 1 2 |
p1 | 1 0 0 0 | 1 7 5 0 |
p2 | 1 3 5 4 | 2 3 5 6 |
p3 | 0 3 3 2 | 0 6 5 2 |
p4 | 0 0 1 4 | 0 6 5 6 |
剩余的总资源为:1 6 2 2
import java.util.ArrayList;
import java.util.Arrays;
/**
* @类名 BankerAlgorithm
* @类说明 银行家算法
* @作者 中都
* @时间 2019/12/2 20:36
*/
public class BankerAlgorithm {
private int[][] allocation; //进程已占有资源
private int[][] need; //进程还需要的资源
private int[] available; //系统剩余可分配资源
private int num = 0; //表示进程数
private ArrayList<Integer> lastRecord = null; //记录最终找到的安全序列
/**
* 银行家算法
* @param allocation
* @param need
* @param available
*/
public void bankerAlgorithm(int[][] allocation,int[][] need,int[] available) {
if(allocation == null || need == null || available == null ||
allocation.length != need.length){
System.out.println("参数不合法!");
return;
}
this.allocation = allocation;
this.need = need;
this.available = available;
this.num = allocation.length;
this.lastRecord = new ArrayList<>();
//执行安全性算法
boolean sign = securityAlgorithm(available.clone(),new boolean[num],0,lastRecord);
if(sign) {
//找到了安全序列,打印结果
show();
}else {
//没有找到
System.out.println("当前状态不存在安全序列!");
}
}
/**
* 安全性算法
* @param work 当前系统剩余资源
* @param flag 标记是否已经分配
* @param count 记录已分配的线程个数
* @param record 记录安全序列
* @return
*/
private boolean securityAlgorithm(int[] work, boolean[] flag, int count, ArrayList<Integer> record) {
if(count >= num) {
//已经找到一个安全序列,停止查找
this.lastRecord = record;
return true;
}
boolean result = false;
for (int i = 0; i < allocation.length; i++) {
//如果当前进程未分配资源并且剩余的资源足够给他分配,那么尝试把资源分配该该进程
if(!flag[i] && adjust(this.need[i],work)) {
//回收资源
int[] temp = work.clone();
recycle(allocation[i],temp);
//标记当前线程已经分配了资源
boolean[] sign = flag.clone();
sign[i] = true;
ArrayList<Integer> tempRecord = (ArrayList)record.clone();
tempRecord.add(i);
result = result || securityAlgorithm(temp,sign,count+1, tempRecord);
}
}
return result;
}
/**
* 回收当前线程占有的资源
* @param resources
* @param work
*/
private void recycle(int[] resources, int[] work) {
for (int i = 0; i < resources.length; i++) {
work[i] += resources[i];
}
}
/**
* 查看剩余的资源是否足够分配给当前进程
* @param need 进程所需的资源
* @param residue 系统剩余的资源
* @return
*/
private boolean adjust(int[] need,int[] residue) {
for (int i = 0; i < need.length; i++) {
if(need[i] > residue[i]) {
return false; //不满足
}
}
return true; //满足
}
/**
* 输出结果
*/
private void show() {
System.out.println("经过查找,找到一个安全序列,资源分配从上至下顺序如下:");
System.out.println("初始时刻剩余总资源:"+Arrays.toString(available));
System.out.println("进程名 拥有资源 需要资源 系统剩余资源 分配后回收系统剩余资源");
for (int i = 0; i < this.lastRecord.size(); i++) {
int index = lastRecord.get(i);
System.out.print(" p"+index);
System.out.print(" "+Arrays.toString(allocation[index]));
System.out.print(" "+Arrays.toString(need[index]));
System.out.print(" "+Arrays.toString(available));
recycle(allocation[index],available);
System.out.println(" "+Arrays.toString(available));
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
int[] l1 = {0,0,3,2};
int[] l2 = {1,0,0,0};
int[] l3 = {1,3,5,4};
int[] l4 = {0,3,3,2};
int[] l5 = {0,0,1,4};
int[][] alreadyOwn = {l1,l2,l3,l4,l5}; //已拥有
int[] m1 = {0,0,1,2};
int[] m2 = {1,7,5,0};
int[] m3 = {2,3,5,6};
int[] m4 = {0,6,5,2};
int[] m5 = {0,6,5,6};
int[][] need = {m1,m2,m3,m4,m5}; //需要
int[] residue = {1,6,2,2}; //剩余
new BankerAlgorithm().bankerAlgorithm(alreadyOwn,need,residue);
}
}
运行结果:
3、检测与接触死锁
当线程发现进入死锁了,立即从死锁状态解除掉,采用方式剥夺资源:从其他线程剥夺足够多的资源给死锁线程,以免除死锁状态的线程;
4、如何检查程序中的死锁?
实际定位死锁问题的思路:
- 首先需要确定java进程是否发生死锁
- 使用在Java JDK目录bin目录下的工具jvisualvm或命令jstack;
- ①打开jvisualvm工具,专门分析JVM CPU,内存使用情况,以及线程的运行信息查看当前java进程各个线程运行的状态(颜色);② 通过jvisualvm的线程dump或者jstack命令,把当前java进程所有线程的调用堆栈信息打印出来;/或者使用jstack命令,下面的步骤都是一样的;
- 分析main线程和子线程有没有关键短语: waiting for(资源地址) waiting to lock(资源地址)
- 看线程函数调用栈,定位到源码上,具体问题具体分析;
下面是一个会发生死锁的代码:
package dui;
/**
* @ClassName Test4
* @Description 实验死锁的定位
* @Author lzq
* @Date 2019/8/3 11:36
* @Version 1.0
**/
public class Test4 {
private static Object objA = new Object();
private static Object objB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Thread1());
Thread thread2 = new Thread(new Thread2());
thread1.start();
thread2.start();
}
private static class Thread1 implements Runnable{
@Override
public void run() {
synchronized (objA) {
System.out.println("线程1得到A对象的锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objB) {
System.out.println("线程1得到B对象的锁");
}
}
}
}
private static class Thread2 implements Runnable{
@Override
public void run() {
synchronized (objB) {
System.out.println("线程2得到B对象的锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objA) {
System.out.println("线程2得到A对象的锁");
}
}
}
}
}
定位死锁
1、使用jvisualvm工具
打开jvisualvm工具,运行上面程序:
点击Dump:
可以定位到,在33、51行有问题;
2、使用jstack命令
先打开命令行窗口,到jdk的bin目录下,运行程序,输入jps命令,查看正在运行的进程号:
输入jstack+进程号:
往下找:
引起死锁的案例:
1、生产者、消费者使用不当会产生死锁(wait、notify、notifyAll)
2、多线程获取多把锁
lock1 lock2
Lock1.lock();
lock2.lock();
//todo
lock2.unlock();
lock1.unlock();
3、哲学家就餐问题
- 圆桌上有5位哲学家、每两位中间有一个筷子
- 每个哲学家有两件事要做 思考、吃饭(哲学家必须同时拿到两个筷子才能吃饭)
- 哲学家之间并不知道对方何时要吃饭、何时要思考,不能协商制定吃饭、思考策略
- 制定一个拿筷子的策略,使得哲学家不会因为拿筷子而出现死锁乐观锁
哲学家就餐问题:
这个问题我的解决办法是加入一个服务员类,由服务员发放和回收筷子,用以解决死锁问题:
/**
* @ClassName Philosopher
* @Description 哲学家
* @Author lzq
* @Date 2019/4/29 0:02
* @Version 1.0
**/
public class Philosopher extends Thread{
private Waiter waiter = null;
public Philosopher(Waiter waiter){
this.waiter = waiter;
}
@Override
public void run() {
while (true) {
waiter.allocation();
}
}
}
/**
* @ClassName Chopsticks
* @Description 筷子
* @Author lzq
* @Date 2019/4/28 23:00
* @Version 1.0
**/
public class Chopsticks {
private int number = 0; //筷子的编号
public Chopsticks(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
import java.util.Random;
/**
* @ClassName Waiter
* @Description 服务员
* @Author lzq
* @Date 2019/4/28 23:14
* @Version 1.0
**/
public class Waiter {
//存放5只筷子的数组
private Chopsticks[] chopsticks = new Chopsticks[5];
public Waiter(Chopsticks chopsticks1, Chopsticks chopsticks2, Chopsticks chopsticks3,
Chopsticks chopsticks4, Chopsticks chopsticks5) {
chopsticks[0] = chopsticks1;
chopsticks[1] = chopsticks2;
chopsticks[2] = chopsticks3;
chopsticks[3] = chopsticks4;
chopsticks[4] = chopsticks5;
}
Random random = new Random();
/**
* 分配筷子
*/
public synchronized void allocation() {
try {
int x1 = random.nextInt(5);
int x2 = random.nextInt(5);
while (x1 == x2) {
x2 = random.nextInt(5);
}
Chopsticks chop1 = chopsticks[x1];
Chopsticks chop2 = chopsticks[x2];
String name = Thread.currentThread().getName();
System.out.println(name+"拿到"+chop1.getNumber()+"号筷子、"+chop2.getNumber()+"号筷子,开始就餐......");
Thread.sleep(500);
System.out.println(name+"就餐完毕,放下筷子,开始思考......");
System.out.println("---------------------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* @ClassName TestDemo25
* @Description 哲学家就餐问题
* @Author lzq
* @Date 2019/4/28 22:58
* @Version 1.0
**/
public class TestDemo25 {
public static void main(String[] args) {
//5只筷子
Chopsticks chopsticks1 = new Chopsticks(1);
Chopsticks chopsticks2 = new Chopsticks(2);
Chopsticks chopsticks3 = new Chopsticks(3);
Chopsticks chopsticks4 = new Chopsticks(4);
Chopsticks chopsticks5 = new Chopsticks(5);
Waiter waiter = new Waiter(chopsticks1,chopsticks2,chopsticks3,chopsticks4,chopsticks5);
Thread t1 = new Philosopher(waiter);
Thread t2 = new Philosopher(waiter);
Thread t3 = new Philosopher(waiter);
Thread t4 = new Philosopher(waiter);
Thread t5 = new Philosopher(waiter);
t1.setName("哲学家1");
t2.setName("哲学家2");
t3.setName("哲学家3");
t4.setName("哲学家4");
t5.setName("哲学家5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
这个解法我觉得不好,虽然保证了并发问题,但是有点不合实际,5筷子、5个哲学家,同时是可以有两个人吃饭的,我这个每次只能有一个人吃饭,还有其他的解决办法,我觉得那个哲学家在拿筷子的时候,确保左右都有筷子才同时拿起左右两只筷子比较好:
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName Chopsticks
* @Description 筷子
* @Author lzq
* @Date 2019/8/4 19:45
* @Version 1.0
**/
public class Chopsticks {
private int count = 5;
private List<Boolean> chops = new ArrayList<>();
public Chopsticks(int count) {
this.count = count;
for (int i = 1; i <= this.count; i++) {
chops.add(false);
}
}
/**
* 获取筷子
*/
public synchronized void getChop() {
String threadName = Thread.currentThread().getName();
int index = Integer.valueOf(threadName);
int left = index % 5;
int right = (index + 1) % 5;
while (chops.get(left) || chops.get(right)) {
//拿左右两边的筷子 进来就意味着至少有一只筷子被别人占用
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//出了上面的循环,就意味着当前哲学家可以吃饭了,占用筷子
chops.set(left, true);
chops.set(right, true);
System.out.println(threadName + " 哲学家拿到 " + left + "号筷子和" + right + "号筷子 开始就餐...");
}
/**
* 放下筷子,两个操作写开,就可以同时两个人用四个筷子吃饭了
*/
public synchronized void freeChop() {
String threadName = Thread.currentThread().getName();
int index = Integer.valueOf(threadName);
int left = index % 5;
int right = (index + 1) % 5;
System.out.println(threadName + " 哲学家放下 " + left + "号筷子和" + right + "号筷子 开始思考...");
chops.set(left, false);
chops.set(right, false);
notifyAll();
}
}
/**
* @ClassName Philosopher
* @Description 哲学家
* @Author lzq
* @Date 2019/8/4 20:08
* @Version 1.0
**/
public class Philosopher extends Thread{
private Chopsticks chopsticks;
public Philosopher(Chopsticks chopsticks) {
this.chopsticks = chopsticks;
}
@Override
public void run() {
while (true) {
this.chopsticks.getChop();
try {
Thread.sleep(500); //模拟吃饭时间
} catch (InterruptedException e) {
e.printStackTrace();
}
this.chopsticks.freeChop();
}
}
}
/**
* @ClassName Test
* @Description 哲学家就餐
* @Author lzq
* @Date 2019/8/4 20:10
* @Version 1.0
**/
public class Test {
public static void main(String[] args) {
Chopsticks chopsticks = new Chopsticks(5);
Philosopher philosopher1 = new Philosopher(chopsticks);
Philosopher philosopher2 = new Philosopher(chopsticks);
Philosopher philosopher3 = new Philosopher(chopsticks);
Philosopher philosopher4 = new Philosopher(chopsticks);
Philosopher philosopher5 = new Philosopher(chopsticks);
philosopher1.setName("1");
philosopher2.setName("2");
philosopher3.setName("3");
philosopher4.setName("4");
philosopher5.setName("5");
philosopher1.start();
philosopher2.start();
philosopher3.start();
philosopher4.start();
philosopher5.start();
}
}