Java并发编程之死锁相关知识整理

一、什么是死锁

所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进

二、死锁产生的条件

以下将介绍死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁

互斥条件

进程要求对所分配的资源(如打印机〉进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待

不可剥夺条件

进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)

请求与保持条件

进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放

循环等待条件

存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求s即存在一个处于等待状态的进程集合{PI, P2,…,, pn}

其中Pi等待的资源被P(i+1)占有( i=0,1,… , n-1),n等待的资源被Po占有

但也有可能Pi等待的资源被P(i+1)占有( i=0,1,… , n-1),但可以通过圈外也获取资源(不死锁),如图所示

三、死锁产生的演示

接下来我们创建示例类,通过不同线程来获取不同的锁看看

public class Deadlock implements Runnable {

	private int flag;//用于区分走向

	//对象锁 static 使不同线程引用的都是同一地址
	private static Object obj1 =new Object();

	//对象锁 static 使不同线程引用的都是同一地址
	private static Object obj2 =new Object();

	public Deadlock(int flag) {
        this.flag = flag;
    }

	public void run(){

		if(flag == 1){
			synchronized (obj1){
				System.out.println(Thread.currentThread().getName ()
						+ "获取Obj1,需要请求Obj2");
				try{
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (obj2){
					System.out.println(Thread.currentThread().getName ()
						+ "已获取Obj1、获取Obj2");
				}
			}
		}else{
			synchronized (obj2){
				System.out.println(Thread.currentThread().getName ()
						+ "获取Obj2,需要请求Obj1");
				try{
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (obj1){
					System.out.println(Thread.currentThread().getName ()
						+ "已获取Obj2、获取Obj1");
				}
			}
		}
	}
}

这时我们创建两个线程, 执行这两个obj的锁,看看是否会产生死锁

class DeadlockTest {
    public static void main(String[] args) {

        Thread thread1 = new Thread(new Deadlock(1),"线程1");
        Thread thread2 = new Thread(new Deadlock(2),"线程2");

        thread1.start();
        thread2.start();
    }
}
//运行结果如下:
线程1获取Obj1,需要请求Obj2
线程2获取Obj2,需要请求Obj1

我们发现并没有已获取obj1、obj2或者以获取obj2、获取obj1 的输出,因为他们满足了死锁产生的条件

四、死锁的预防

预防死锁是设法至少破坏产生死锁的四个必要条件之一严格的防止死锁的出现

破坏互斥条件

“互斥”条件是无法破坏的。在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件

破坏“占有并等待”条件

破坏“占有并等待”条件,就是在系统中不允许进程在已获得某种资源的情况下,申请其他资源

即要想出一个办法,阻止进程在持有资源的同时申请其他资源,有以下思路可提供:

  • 方法一:即创建进程时,要求它申请所需的全部资源,系统或满足其所有要求,或什么也不给它
  • 方法二:要求每个进程提出新的资源申请前,释放它所占有的资源

这样一个进程在需要资源A时,须先把它先前占有的资源R释放掉,然后才能提出对A的申请,即使它可能很快又要用到资源R

破坏“不可抢占”条件

破坏“不可抢占”条件就是允许对资源实行抢夺

如果占有某些资源的一个进程进行下一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源

如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另一个进程,要求它释放资源。只有在任意两个进程的优先级都不相同的条件下,方法二才能预防死锁

破坏“循环等待”条件

破坏“循环等待”条件的一种方法,是将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。这样做就能保证系统不出现死锁。

五、死锁的避免

死锁的语法是是严格限制产生死锁的条件,避免死锁的方式不严格限制,因为即使死锁的必要条件存在,也不一定发生死锁。而是让程序通过算法再满足条件后避免死锁

避免方法:有序资源分配算法

该算法实现步骤如下:

  • 必须为所有资源统一编号,例如打印机为1、传真机为2、磁盘为3等
  • 同类资源必须一次申请完,例如打印机和传真机一般为同一个机器必须同时申请
  • 不同类资源必须按顺序申请

举例:有两个进程P1和P2,有两个资源R1和R2,P1与P2线程、分别请求资源:R1、R2

P1先获取R1、R2,而P2就请求等待P1释放,这样就破坏了环路条件,避免了死锁的发生

避免方法:银行家算法

银行家算法(Banker's A1gorithm)是一个避免死锁(Dead1ock)的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.HE系统设计的一种避免死锁产生的算法

它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。流程图如下:

避免方法:顺序加锁

当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生

我们上面的示例代码就是这样的情况,线程1请求Obj1、Obj2,线程2请求Obj2、Obj1

而我们如果能够保证所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生

列如我们线程1请求Obj1、Obj2,线程2请求Obj1、Obj2

按照顺序加锁是一种有效的死锁预防机制。但是这种方式需要事先知道所有可能会用到的锁,但总有些时候是无法预知的,所以该种方式只适合特定场景

避免方法:限时加锁

限时加锁是线程在尝试获取锁的时候加一个超时时间,若超过这个时间则放弃对该锁请求,并回退并释放所有已经获得的锁,然后等待一段随机的时间再重试

以下展示了两个线程以不同的顺序尝试获取相同的两个锁,在发生超时后回很并重试的场景:

//线程 1 锁定A
Thread 1 locks A

//线程 2  锁定B
Thread 2 locks B

//线程 1 尝试去锁定B,但已被锁定
Thread 1 attempts to lock 8 but is blocked

//线程 2 尝试去锁定A,但已被锁定
Thread 2 attempts to lock A but is blocked

//线程 1 等待锁定B的时间超时了
Thread 1' s lock attempt on B times out

//线程 1 进行回退并释放锁定A的资源
Thread 1 backs up and releases A as well

//线程 1 等待一段时间再重试获取
Thread 1 waits randomly (e.g. 257 millis) before retrying
Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g.43 millis) before retrying

在上面的例子中,线程2比线程1早200毫秒进行重试加锁,因此它可以先成功地获取到两个锁,这时线程1尝试获取锁A并且处于等待状态,当线程2结束时,线程1也可以顺利的获得这两个锁

这种方式有两个缺点:

  • 当线程数量少时,该种方式可避免死锁,但当线程数量过多,这些线程的加锁时限相同的概率就高很多,可能会导致超时后重试的死循环
  • Java中不能对synchronized同步块设置超时时间,你需要创建自定义锁或使用Java5中 java .util.concurrent包下的工具

到此这篇关于Java并发编程之死锁相关知识整理的文章就介绍到这了,更多相关Java死锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java线程死锁实例及解决方法

    这篇文章主要介绍了Java线程死锁实例及解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.死锁的定义 所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进 2.死锁产生的必要条件 互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个线程所占有.此时若有线程请求该资源,则请求线程只能等待. 不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程倾向夺

  • Java中的线程死锁是什么?如何避免?

    认识线程死锁 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.由于线程被无限期地阻塞,因此程序不可能正常终止. 如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态. 下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于<并发编程之美>): public class DeadLockDemo { private static Object resource1 = new Object

  • Java项目有中多个线程如何查找死锁

    当项目有中多个线程,如何查找死锁? 最近,在IDEA上进行多线程编程中老是在给线程加锁的时候,总是会遇到死锁问题,而当程序出现死锁问题时,编译器不能精确的显示错误的精确位置.当项目代码很多的时候, 往往会给自己添加不必要的麻烦,今天,我就分享分享几个解决方法. 1.编译环境 IDEA 2020 ,windows10, jdk8及以上版本 一.死锁是什么? 死锁指A线程想使用资源但是被B线程占用了,B线程线程想使用资源被A线程占用了,导致程序无法继续下去了. 1.1 死锁的例子: public c

  • Java多线程产生死锁的必要条件

    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行.当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块.当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁. 死锁是操作系统里里面的一个重要的概念,死锁通常发生在并发的场景里. 死锁是多个进程或线程,彼此争抢资源而陷入僵局的一种情况. 在笔者参加的多次

  • java中常见的死锁以及解决方法代码

    在java中我们常常使用加锁机制来确保线程安全,但是如果过度使用加锁,则可能导致锁顺序死锁.同样,我们使用线程池和信号量来限制对资源的使用,但是这些被限制的行为可能会导致资源死锁.java应用程序无法从死锁中恢复过来,因此设计时一定要排序那些可能导致死锁出现的条件. 1.一个最简单的死锁案例 当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞.在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去.这种就是最简答的死锁形式(

  • Java多线程死锁示例

    本文实例演示了Java多线程死锁.分享给大家供大家参考,具体如下: package com.damlab.fz; public class DeadLock { public static void main(String[] args) { Resource r1 = new Resource(); Resource r2 = new Resource(); // 每个线程都拥有r1,r2两个对象 Thread myTh1 = new MyThread1(r1, r2); Thread myT

  • 详解java中产生死锁的原因及如何避免

    1. Java中导致死锁的原因 Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞了.导致了死锁.这是最容易理解也是最简单的死锁的形式.但是实际环境中的死锁往往比这个复杂的多.可能会有多个线程形成了一个死锁的环路,比如:线程T1持有锁L1并且申请获得锁L2,而线程T2持有锁L2并且申请获得锁L3,而线程T3持有锁L3并且申请获得锁L1,这样导致了一个锁依赖的环路:T1依

  • 基于Java信号量解决死锁过程解析

    死锁在多线程的情况下,会出现数据不同步情况, 而为了避免这种情况,之前也说了:界区实现方法有两种,一种是用synchronized,一种是用Lock显式锁实现. 而如果不恰当的使用了锁,且出现同时要锁多个对象时,会出现死锁情况,如下: package lockTest; import java.util.Date; /** * 崔素强 * @author cuisuqiang@163.com */ public class LockTest { public static String obj1

  • Java并发编程预防死锁过程详解

    这篇文章主要介绍了Java并发编程预防死锁过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在java并发编程领域已经有技术大咖总结出了发生死锁的条件,只有四个条件都发生时才会出现死锁: 1.互斥,共享资源X和Y只能被一个线程占用 2.占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X 3.不可抢占,其他线程不能强行抢占线程T1占有的资源 4.循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有

  • 浅谈Java由于不当的执行顺序导致的死锁

    我们来讨论一个经常存在的账户转账的问题.账户A要转账给账户B.为了保证在转账的过程中A和B不被其他的线程意外的操作,我们需要给A和B加锁,然后再进行转账操作, 我们看下转账的代码: public void transferMoneyDeadLock(Account from,Account to, int amount) throws InsufficientAmountException { synchronized (from){ synchronized (to){ transfer(fr

随机推荐