怎么处理JAVA多线程死锁问题?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怎么处理JAVA多线程死锁问题?相关的知识,希望对你有一定的参考价值。
应当如何设置一个多例的线程,同时又要关注程序的死锁问题?
参考技术A 对资源采用同步,还有银行家算法银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系 银行家算法
统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构。 要解释银行家算法,必须先解释操作系统安全状态和不安全状态。 安全序列是指一个进程序列P1,…,Pn是安全的,如果对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和。
安全状态
如果存在一个由系统中所有进程构成的安全序列P1,…,Pn,则系统处于安全状态。安全状态一定是没有死锁发生。
不安全状态
不存在一个安全序列。不安全状态不一定导致死锁。
算法的实现
初始化
由用户输入数据,分别对可利用资源向量矩阵AVAILABLE、最大需求矩阵MAX、分配矩阵ALLOCATION、需求矩阵NEED赋值。
银行家算法
在避免死锁的方法中,所施加的限制条件较弱,有可能获得令人满意的系统性能。在该方法中把系统的状态分为安全状态和不安全状态,只要能使系统始终都处于安全状态,便可以避免发生死锁。 银行家算法的基本思想是分配资源之前,判断系统是否是安全的;若是,才分配。它是最具有代表性的避免死锁的算法。 设进程cusneed提出请求REQUEST [i],则银行家算法按如下规则进行判断。 (1)如果REQUEST [cusneed] [i]<= NEED[cusneed][i],则转(2);否则,出错。 (2)如果REQUEST [cusneed] [i]<= AVAILABLE[cusneed][i],则转(3);否则,出错。 (3)系统试探分配资源,修改相关数据: AVAILABLE[i]-=REQUEST[cusneed][i]; ALLOCATION[cusneed][i]+=REQUEST[cusneed][i]; NEED[cusneed][i]-=REQUEST[cusneed][i]; (4)系统执行安全性检查,如安全,则分配成立;否则试探险性分配作废,系统恢复原状,进程等待。
安全性检查算法
(1)设置两个工作向量Work=AVAILABLE;FINISH (2)从进程集合中找到一个满足下述条件的进程, FINISH==false; NEED<=Work; 如找到,执行(3);否则,执行(4) (3)设进程获得资源,可顺利执行,直至完成,从而释放资源。 Work+=ALLOCATION; Finish=true; GOTO 2 (4)如所有的进程Finish= true,则表示安全;否则系统不安全。 参考技术B 现简述如下:
多线程技术是使程序能够同时完成多项任务的一项技术,java语言内置了对多线程技术的支持,多线程可以使程序同时执行多个程序片段。一个程序可以分为多个任务,每个任务都可以分配给一个线程来实现,在java程序启动时,一个进程马上启动,同时该进程会自动启动一个线程的运行,这个线程称为程序的主线程,该主线程控制其他线程的启动,执行各种关闭操作。
java在类与接口方面提供了对线程的内置支持,任何类如果希望以线程的形式运行,都需要实现接口java.lang.Runnable;或者实现java.lang.Thread类。Runnable接口只有一个run()方法,实现给接口只有重写该方法,而Thread类也实现了Runnable接口,但该类有更丰富的方法。常用的方法包括start(),run(),start()方法用于启动线程,而run()方法是线程的主体方法。线程完成的功能代码都写在这个方法里。
例子如下:
public class MyThread1 extends Thread
public static void main(String [] args)
Thread t=Thread.currentThread();//获得当前主线程的引用
System.out.println("当前主线程是 "+t);
t.setName("MyThread1");//改变当前主线程的名称
System.out.println("当前主线程是 "+t);
MyThread1 m=new MyThread1();//创建了子线程
mt.start();//启动子线程
public void run()
int sum=0;
for(int i=0;i<101;i++)
sum+=i;
System.out.println("1+2+3+...="+sum);
关于进线程的同步问题:
通过线程的优先级可以设置线程占用cpu时间的策略,保证多个线程能合理的顺利的占用cpu时间,执行自己的run()方法,但是如果出现多个线程同时操作一个共享的资源,怎么处理?
为此,java提供了线程的同步机制,所谓同步机制是指两个或多个线程同时访问一个对象时,应该保持数据的统一性与完整性,一旦某个线程获得控制权后,其他的线程只能等待,知道原先的线程放弃对黑盒子的控制。
有时间再说,有事...不懂联系我
Java多线程死锁问题
死锁这么重要,请仔细阅读
死锁问题
死锁定义
多线程编程中,因为抢占资源造成了线程无限等待的情况,此情况称为
死锁
。
死锁举例
注意:线程和锁的关系是:一个线程可以拥有多把锁,一个锁只能被一个线程拥有。
当两个线程分别拥有一把各自的锁之后,又尝试去获取对方的锁,这样就会导致死锁情况的发生,具体先看下面代码:
/**
* 线程死锁问题
*/
public class DeadLock {
public static void main(String[] args) {
//创建两个锁对象
Object lock1 = new Object();
Object lock2 = new Object();
//创建子线程
/*
线程1:①先获得锁1 ②休眠1s,让线程2获得锁2 ③线程1尝试获取锁2 线程2同理
*/
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//线程1业务逻辑
synchronized(lock1){
System.out.println("线程1得到了锁子1");
try {
//休眠1s,让线程2先得到锁2
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1尝试获取锁2...");
synchronized(lock2){
System.out.println("线程1获得了锁2!");
}
}
}
},"线程1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//线程2业务逻辑
synchronized(lock2){
System.out.println("线程2得到了锁子2");
try {
//休眠1s,让线程1先得到锁1;因为线程是并发执行我们不知道谁先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2尝试获取锁1...");
synchronized(lock1){
System.out.println("线程2获得了锁1");
}
}
}
},"线程2");
thread1.start();
thread2.start();
}
}
程序运行结果如下:
可以看出,线程1尝试获取了锁2,线程2尝试获取了锁1,但是二者并没有获取到对方的锁;这就发生了所谓的“死锁”!
如何排查死锁
想要排查死锁具体细节,可以通过三个工具(位于jdk安装路径bin目录)去排查,现在就给大家介绍一下:
1.jconsole
可以看出,线程1和线程2发生了死锁,死锁发生的位置一目了然
2.jvisualvm
可以看出,发生了死锁,线程1和线程2尝试获取的锁是对方的锁。
3.jmc
可以看出,同样检测出了死锁情况
无论是用哪个工具排查死锁情况都是OK的。
死锁发生的条件
1.互斥条件(一个锁只能被一个线程占有,当一个锁被一个线程持有之后,不能再被其他线程持有);
2.请求拥有(一个线程拥有一把锁之后,又去尝试请求拥有另外一把锁);可以解决
3.不可剥夺(一个锁被一个线程占有之后,如果该线程没有释放锁,其他线程不能强制获得该锁);
4.环路等待条件(多线程获取锁时形成了一个环形链)可以解决
怎么解决死锁问题?
环路等待条件相对于请求拥有更容易实现,那么
通过破坏环路等待条件
解决死锁问题
破坏环路等待条件示意图:
针对于上面死锁举例中代码,解决死锁,具体看下面代码:
public class SolveDeadLock {
public static void main(String[] args) {
//创建两个锁对象
Object lock1 = new Object();
Object lock2 = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//线程1业务逻辑
synchronized(lock1){
System.out.println("线程1得到了锁子1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1尝试获取锁2...");
synchronized(lock2){
System.out.println("线程1获得了锁2!");
}
}
}
},"线程1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//线程2业务逻辑
synchronized(lock1){
System.out.println("线程2得到了锁子1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2尝试获取锁2...");
synchronized(lock2){
System.out.println("线程2获得了锁2");
}
}
}
},"线程2");
thread1.start();
thread2.start();
}
}
程序运行结果如下:
可以看出,通过破坏环路等待条件完美解决了死锁问题
线程通讯机制(wait/notify/notifyAll)
定义
线程通讯机制:一个线程的动作可以让另外一个线程感知到,这就是线程通讯机制。
wait():让当前线程进入休眠等待状态;
notify():唤醒当前对象上的休眠等待线程;
notifyAll():唤醒当前对象上的所有休眠等待线程。
相关面试重点
面试问题:
1.wait()使用时为什么需要加锁?
因为wait()必须在同步方法或者同步块中使用,也就是说wait()需要配合加锁一起使用(比如synchronized或Lock),调用对象调用wait()如果没有适当的锁,就会引发异常,因此说wait()使用时需要加锁。
2.wait()使用为什么要释放锁?
wait()是Objetc类中一个实例方法,默认是不传任何值的,不传值的时候表示让当前线程处于永久休眠等待状态,这样会造成一个锁被一个线程长时间一直拥有,为了避免这种问题的发生,使用wait()后必须释放锁。
wait()/notify()/notifyAll()使用时注意事项:
使用这三个方法时都必须进行加锁;
2.加锁的对象和调用wait()/notify()/notifyAll()对象必须是同一个对象;
3.一组wait()/notify()/notifyAll()必须是同一个对象;
4.notify()只能唤醒当前对象上的一个休眠等到线程;而notifyAll()可以唤醒当前对象上的所有休眠等待线程。
sleep(0)和wait(0)的区别:
1.sleep()是Thread类中一个静态方法,wait()是Object类中一个普通的成员方法;
2.sleep(0)会立即触发一次CPU的抢占执行,wait(0)会让当前线程无限休眠等待下去。
wait()和sleep()的区别:
相同点:
1.都会让当前线程进行休眠等待;
2.使用二者时都需处理InterruptedException异常(try/catch)。
不同点:
1.wait()是Object中普通成员方法,sleep是Thread中静态方法;
2.wait()使用可以不穿参数,sleep()必须传入一个大于等于0的参数;
3.wait()使用时必须配合加锁一起使用,sleep()使用时不需要加锁;
4.wait()使用时需要释放锁,如果sleep()加锁后不会释放锁;
5.wait()会让当前线程进入WAITING状态(默认没有明确的等待时间,当被别的线程唤醒或者wait()传参后超过等待时间量自己唤醒,将进入就绪状态),sleep()会让当前线程进入TIMED_WAITING状态(有明确的结束等待时间,但是这是死等的方式,休眠结束后进入就绪状态)。
*为什么wait()处于Object中而不是Thread中?(有点绕 我有点懵了…)
wait()的调用必须进行加锁和释放锁操作,而锁是属于对象级别非线程级别,也就是说锁针对于对象进行操作而不是线程;而线程和锁是一对多的关系,一个线程可以拥有多把锁,而一个线程只能被一个线程拥有,为了灵活操作,就将wait()放在Object中。
LockSupport
LockSupport是对wait()的升级,无需加锁也无需释放锁;
- LockSupport.park()让线程休眠,和wait()一样会让线程进入WAITING状态;
- LockSupport.unpark()唤醒线程,可以唤醒对象上指定的休眠等待线程;(优势)
LockSupport与wait()区别
wait()与LockSupport的区别:
相同点:
1.二者都可以让线程进入休眠等待状态;
2.二者都可以传参或者不传参,让线程都会进入到WAITING状态。
不同点:
1.wait()需要配合加锁一起使用,LockSupport无需加锁;
2.wait()只能唤醒对象的随机休眠线程和全部线程,LockSupport可以唤醒对象的指定休眠线程。
以上是关于怎么处理JAVA多线程死锁问题?的主要内容,如果未能解决你的问题,请参考以下文章