锁活跃性问题(死锁,饥饿、活锁)

一、死锁

        每个人都拥有其他人需要的资源,同时又等待其他人已经拥有的资源,并且每个人在获取所有需要的资源之前都不会放弃已经拥有的资源。

        当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞,这个我们都知道。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。其中多个线程因为存在环路锁依赖关系而永远地等待下去。

         在数据库系统的设计中考虑了监测死锁以及从死锁中恢复,数据库如果监测到了一组事物发生了死锁时,将选择一个牺牲者并放弃这个事物。Java虚拟机解决死锁问题方面并没有数据库这么强大,当一组Java线程发生死锁时,这两个线程就永远不能再使用了,并且由于两个线程分别持有了两个锁,那么这两段同步代码/代码块也无法再运行了----除非终止并重启应用。

1、判断死锁总结:把每个线程假象成有向图中的一个节点,图中每条边的关系是:“线程A等待线程B所占有的资源”。如果在图中形成了一条环路,那么久存在一个死锁。

2、死锁的必要条件:

a、互斥条件:一个资源每次只能被一个进程使用
b、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
c、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
d、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

3、死锁的几种常见常见

(1)、锁顺序死锁(LeftRightDeadLock):两个线程以不同的顺序 来获得相同的锁。

public class LeftRightDeadLock
{
    private final Object left = new Object();
    private final Object right = new Object();
    
    public void leftRight() throws Exception
    {
        synchronized (left)
        {
            Thread.sleep(5000);
            synchronized (right)
            {
                System.out.println("leftRight end!");
            }
        }
    }
    
    public void rightLeft() throws Exception
    {
        synchronized (right)
        {
            Thread.sleep(5000);
            synchronized (left)
            {
                System.out.println("rightLeft end!");
            }
        }
    }
}

如果所有线程以固定的顺序来获取锁,就不会出现死锁。

如LeftRightDeadLock每个线程都需要获取left和right两个锁,如果都是按照left、right或者right、left的顺序获得锁就不会发生死锁,一旦一个线程按照leftright顺序而另外一个rightleft顺序就有可能发生死锁

(2)、动态的锁顺序死锁(transferMoney)

public static void transferMoney(Account fromAccount,Account toAccount,long amount){
        synchronized (fromAccount){
            synchronized (toAccount){
                fromAccount.debit(amount);
                toAccount.credit(amount);
            }
        }
    }

A:transferMoney(myaccount,youraccount,10)

B:transferMoney(youraccount,myaccount,20)

如果执行时序不当,A可能获得myaccount锁并等待youraccount锁,而B可能获得youraccount锁而等待myaccount锁

(3)、在协作对象之间发生的死锁

如果在持有锁时调用某个外部方法,那么可能产生死锁,因为在这个外部方法中可能会获取其他的锁或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

因此,应该避免锁所作用的范围内调用外部的方法。

(4)、开发调用

减少锁的范围,把不需要加锁的代码移除锁的范围域,比如,把使用synchronized同步的方法,改成使用synchronized同步部分代码块。

(5)、资源死锁

二、饥饿

当线程由于无法访问它所需要的资源而不能继续执行时,就发生了“饥饿”。

例如:如果java应用程序中对线程的优先级使用不当,或者在持有锁时执行一些无法结束的结构(死循环或者无限制地等待某个资源),那么可能导致饥饿,因为其他需要这个锁的线程将无法得到它。

总结:要避免使用线程的优先级,因为这会增加平台的依赖性,并导致死锁。在大多数的并发程序中都使用默认的线程优先级。

三、活锁

该问题不会阻塞线程,但是也不能继续执行,因为线程将不断重复执行相同的操作,而且总是失败。

要解决这种活锁问题,需要在失败重试机制中引入随机性。

猜你喜欢

转载自blog.csdn.net/l1394049664/article/details/81275465
今日推荐