b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

多线程死锁的产生以及如何避免死锁

电脑杂谈  发布时间:2020-05-07 00:14:48  来源:网络整理

解决线程死锁_多线程死锁的例子_线程 死锁

多线程和多进程提高了系统资源的利用率,并提高了系统的处理能力. 但是,并发执行还会带来新的问题僵局. 所谓死锁是指由多个线程竞争资源而导致的死锁(彼此等待). 没有外力,这些进程将无法前进.

这里有一些例子来说明死锁现象.

让我们看看生活中的一个例子. 两个人一起吃饭,但只有一双筷子. 两个人轮流吃饭(只能同时吃两个筷子). 在某个时间,一个人拿了左筷子,一个人拿了右筷子. 他们两个都同时占用一个资源,并等待另一资源. 这时,A正在等待B完成进食并释放它占用的筷子. 等待A完成吃掉并释放它拥有的筷子,这样他就陷入了无限循环,没有人可以继续吃东西. .

计算机系统中也存在类似情况. 例如,计算机系统中只有一台打印机和一个输入设备. 过程P1正在占用输入设备,并发出了使用打印机的请求,但是此时打印机正在被过程P2占用,并且P2在打印机之前未释放. 还要求使用被P1占用的输入设备. 这样,两个进程将无限地等待彼此,并且两个进程都无法继续执行. 这时,两个进程陷入死锁状态.

1)系统资源竞争

系统中通常不可分割的资源数量不足以满足运行多个进程的需求,因此在运行过程中,进程将因争用资源而陷入僵局,例如磁带驱动器和打印机. 只有对不可剥夺资源的竞争才会造成僵局,而对可剥夺资源的竞争也不会造成僵局.

2)处理进度命令不合法

在运行过程中,请求和释放资源的不正确顺序也将导致死锁. 例如,并发进程P1和P2分别维护资源R1和R2,而进程P1申请资源R2,而进程P2申请资源R1,由于所需的资源已被占用,它们都将被阻塞.

不正确使用信号量也可能导致死锁. 进程彼此等待对方的消息,因此,这些进程无法继续前进. 例如,进程A在等待进程B发送的消息,而进程B在等待进程A发送的消息. 可以看出多线程死锁的例子,进程A和B不是在争夺相同的资源,而是在等待每个资源. 他人的资源导致僵局.

解决线程死锁_线程 死锁_多线程死锁的例子

3)产生死锁的必要条件

必须同时满足以下四个条件才能产生死锁. 只要这些条件中的任何一个都不成立多线程死锁的例子,就不会发生死锁.

直觉上,循环等待条件似乎与死锁的定义相同,但事实并非如此. 根据死锁定义,形成等待循环所需的条件更加严格. 它要求等待Pi的资源必须满足P(i + 1),而循环等待条件没有这种限制. 例如,系统中有两个输出设备,P0占据一个,PK占据另一个,而K不属于集合{0,1,...,n}.

Pn正在等待输出设备,该设备可以从P0或PK获得. 因此,尽管Pn,P0和其他一些过程形成一个循环等待循环,但PK不在循环中. 如果PK释放输出设备,则可以中断循环等待,如图2-16所示. 因此,循环等待只是死锁的必要条件.

资源分配图包含圆圈并且系统不一定具有死锁的原因是相似资源的数量大于1. 但是,如果系统中每种资源的类型只有一个,在资源分配图中包含圆圈成为系统死锁的必要和充分条件.

死锁的例子

/** 
* 一个简单的死锁类 
* 当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();  
    @Override  
    public void run() {  
        System.out.println("flag=" + flag);  
        if (flag == 1) {  
            synchronized (o1) {  
                try {  
                    Thread.sleep(500);  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
                synchronized (o2) {  
                    System.out.println("1");  
                }  
            }  
        }  
        if (flag == 0) {  
            synchronized (o2) {  
                try {  
                    Thread.sleep(500);  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
                synchronized (o1) {  
                    System.out.println("0");  
                }  
            }  
        }  
    }  
  
    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()之前运行  
        new Thread(td1).start();  
        new Thread(td2).start();  
  
    }  
}  

在某些情况下,可以避免死锁. 避免死锁的三种技术:

解决线程死锁_多线程死锁的例子_线程 死锁

锁定顺序(线程以特定顺序锁定)锁定时间限制(当线程尝试获取锁定时,添加了特定时间限制,如果超过了时间限制,则放弃对锁定的请求,并锁定释放)死锁检测

锁定顺序

当多个线程需要相同的锁,但以不同的顺序锁定它们时,很容易发生死锁.

如果可以确保所有线程以相同的顺序获取锁,则不会发生死锁. 看这个例子:

Thread 1:
  lock A 
  lock B
Thread 2:
   wait for A
   lock C (when A locked)
Thread 3:
   wait for A
   wait for B
   wait for C

如果线程(例如线程3)需要一些锁,则必须按一定顺序获取锁. 只有获得序列前面的锁之后,才能获得序列后面的锁.

例如,线程2和线程3只能在获取锁A之后尝试获取锁C(译者注: 获取锁A是获取锁C的必要条件). 由于线程1已经拥有锁A,因此线程2和3需要等待直到锁A被释放. 然后他们必须成功锁定A才能尝试锁定B或C.

顺序锁定是一种有效的防止死锁的机制. 但是,此方法要求您事先了解所有可能使用的锁(译者注: 以及这些锁的适当顺序),但总有一些情况是不可预测的.

锁定时间限制

解决线程死锁_线程 死锁_多线程死锁的例子

另一种避免死锁的方法是在尝试获取锁时添加超时期限,这意味着如果在尝试获取锁的过程中超过了此时间限制,则线程将放弃锁请求. 如果线程在给定的时间内未成功获取所有必需的锁,它将回滚并释放所有已获取的锁,然后等待随机的时间段再尝试. 这种随机的等待时间为其他线程提供了尝试获取相同锁的机会,并允许应用程序在未获取锁时继续运行. 返回并重复上一个锁的逻辑.

下面是一个示例,该示例显示了一个场景,其中两个线程尝试以不同的顺序获取相同的两个锁,先退后再在发生超时后重试:

Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
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也可以成功获得这两个锁(除非线程2或其他线程程1成功获得这两个锁之前获得了其中一些锁).

应注意,由于锁定超时,我们不能假定在这种情况下发生了死锁. 这也可能是因为获得锁的线程(导致其他线程超时)需要很长时间才能完成其任务.

此外,如果有许多线程同时争用同一批资源,即使存在超时和回滚机制,也可能会导致这些线程反复尝试,但从未获得锁. 如果只有两个线程,并且重试超时设置在0到500毫秒之间,则可能不会出现这种现象,但是如果是10或20个线程,情况就不同了. 因为这些线程等待相等的重试时间的可能性要高得多(或接近以至于它们会引起问题).

(译者注: 超时和重试机制是为了避免同时竞争,但是当线程很多时,两个或更多线程的超时时间将相同或接近的可能性很高. 因此,即使由于竞争而导致超时,但由于超时时间相同,它们将同时开始重试,从而导致新一轮的竞争,从而带来新的问题. )

此机制存在问题. 无法在Java中为同步块设置超时. 您需要创建一个自定义锁,或者使用Java 5中java.util.concurrent包下的工具. 编写自定义锁类并不复杂,但这超出了本文的范围. 后续的Java并发系列将介绍自定义锁的内容.

死锁检测

多线程死锁的例子_线程 死锁_解决线程死锁

死锁检测是一种更好的防止死锁的机制,主要用于无法实现顺序锁并且锁超时不可行的情况.

每当线程获得锁时,它将被记录在与该线程和锁相关的数据结构(映射,图形等)中. 另外,每当线程请求锁定时,也需要将其记录在此数据结构中.

当线程未能请求锁时,线程可以遍历锁关系图以查看是否发生死锁. 例如,线程A请求锁7,但此时锁7由线程B持有. 这时,线程A可以检查线程B是否请求了线程A当前持有的锁. 如果线程B确实有此请求,则发生了死锁(线程A拥有锁1并请求锁7;线程B拥有锁. 7,并请求锁定1).

当然,死锁通常比两个线程互相握住锁更复杂. 线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D再等待线程A. 为了使线程A检测死锁,它需要逐步检测B所请求的所有锁. 从线程B请求的锁开始,线程A找到线程C,然后找到线程D,然后发现线程D本身持有线程D请求的锁. 这是当它知道发生死锁的时候.

下面是四个线程(A,B,C和D)之间的锁拥有和请求之间的关系图. 这样的数据结构可用于检测死锁.

那么当检测到死锁时这些线程会做什么?

一种可行的方法是释放所有锁,回滚并等待随机的一段时间,然后重试. 这类似于简单的锁定超时. 区别在于它仅在发生死锁时回滚,而不是因为锁定请求超时. 尽管存在回滚和等待,但是如果有大量线程在争用同一批锁,它们仍将反复死锁(编者注: 原因与超时类似,因此不能从根本上减少竞争).

更好的解决方案是为这些线程设置优先级,并让一个(或几个)线程回退. 其余线程继续保持所需的锁,就好像没有发生死锁一样. 如果分配给这些线程的优先级是固定的,则同一批线程将始终具有更高的优先级. 为避免此问题,可以在发生死锁时设置随机优先级.


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-201141-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      • 索菲娅罗兰
        索菲娅罗兰

        吨位也大了

        • 丁沙沙
          丁沙沙

          我们公司已经拖欠4个月工资了

      • 王四洋
        王四洋

        这是教育体制落后的具体体现

      • 李夔
        李夔

        房子又买不起

      热点图片
      拼命载入中...