Java多线程编程之死锁

来源:互联网 发布:淘宝店 虎扑识货认证 编辑:程序博客网 时间:2024/06/01 12:34

同步造成的死锁问题是说两(多)个线程互相占用了对方所需要的资源,等待对方释放资源僵持导致程序无人工干预不能结束的情况。
类似于哲学家就餐问题,比如共有3个资源被3个线程访问,每个线程必须拿到2个资源才能保持正常运行。如果这3个线程在同一时刻取得了这3个资源,又没有线程愿意让出资源,这时候就会出现3个线程互相等待其它线程释放资源的情况而导致死锁的出现(或者A依赖于B,B依赖于C,C依赖于A,它们互相取得了对方的资源)。

这里参考一个已经写好的例子对死锁作分析。
Deadlock in Java Example

例:程序设计了一个SyncThread类,类中对对象申请了同步锁(即当对象资源被占用时其它线程不能取得该对象锁。如果暂时无线程取得该对象锁,在同一时间两个线程进入了该同步块中,则两个对象均获得了该锁),而且一个对象锁是在另一个对象锁里面锁定的,work()方法休眠3000ms即代表占用该对象资源3000ms。
新建三个Thread t1,t2,t3分步执行,初始化Runnable类SyncThread类的实例。t1,t2,t3分别申请(obj1,obj2),(obj2,obj3),(obj3,obj1),三个线程分别与其它线程有一个共享的资源。当一个线程获取了两个对象后会自动释放其获取的对象资源,如果一直获取不到两个对象资源就会僵持不下。

public class DeadLock {    public static long begin;    public static void main(String[] args) throws InterruptedException {        begin = System.currentTimeMillis();        Object obj1 = new Object();        Object obj2 = new Object();        Object obj3 = new Object();        Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");        Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");        Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");        t1.start();        Thread.sleep(2000);        t2.start();        Thread.sleep(2000);        t3.start();        long end = System.currentTimeMillis() - begin;        System.out.println("end = " + end);    }}class SyncThread implements Runnable {    private Object obj1;    private Object obj2;    public SyncThread(Object o1, Object o2) {        this.obj1 = o1;        this.obj2 = o2;    }    @Override    public void run() {        String name = Thread.currentThread().getName();        System.out.println(name + " acquiring lock on " + obj1);        synchronized (obj1) {            System.out.println(name + " acquired lock on " + obj1);            work();            System.out.println(name + " acquiring lock on " + obj2);            synchronized (obj2) {                System.out.println(name + " acquired lock on " + obj2);                work();            }            System.out.println(name + " released lock on " + obj2);        }        System.out.println(name + " released lock on " + obj1);        System.out.println(name + " finished execution.");    }    private void work() {        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

查看此程序的结果
t1 acquiring lock on java.lang.Object@61a0353d
t1 acquired lock on java.lang.Object@61a0353d
t2 acquiring lock on java.lang.Object@11b75be2
t2 acquired lock on java.lang.Object@11b75be2
t1 acquiring lock on java.lang.Object@11b75be2
end = 4032
t3 acquiring lock on java.lang.Object@3219ab8d
t3 acquired lock on java.lang.Object@3219ab8d
t2 acquiring lock on java.lang.Object@3219ab8d
t3 acquiring lock on java.lang.Object@61a0353d

可以看出t1、t2、t3一直处于申请资源状态的死锁中,那么为什么会出现这种状态呢?可以分析下线程申请对象资源的时间轴:
这里写图片描述
在0s时候,t1获取obj1对象;
休眠2s后,即第2s,t2获得了obj2对象;
当t1在执行work方法休眠3s后,即第3s,t1想要去获取obj2对象,此时发现obj2对象已经被t2占用了,此时t1被阻塞;
在第2s后休眠的2s后,即第4s,t3获取了obj3对象;
第2s后的3s后(t2执行完work方法),即第5s,t2想要去获取obj3对象,此时obj3对象已经被t3占用了,此时t2被阻塞;
在t3获取obj3对象并执行完work的时刻,即第7s,t3想要去获取obj1对象,此时obj1对象正在阻塞中,被t1占用,此时t3因为不能获取资源而陷入等待阻塞中。
这个时候t1,t2,t3互相占用了对方需要的资源,从此陷入了无尽的等待中。

这个死锁看起来是时间卡得比较紧,那么如果将主函数中的

t2.start();Thread.sleep(2000);

的休眠时间改成3000ms,即更改线程之间的开始运行时间

t2.start();Thread.sleep(3000);

又会出现什么样的效果呢?

结果如下:
t1 acquiring lock on java.lang.Object@5d0769dd
t1 acquired lock on java.lang.Object@5d0769dd
t2 acquiring lock on java.lang.Object@1cf15b84
t2 acquired lock on java.lang.Object@1cf15b84
t1 acquiring lock on java.lang.Object@1cf15b84
t2 acquiring lock on java.lang.Object@334dcfad
end = 5016
t2 acquired lock on java.lang.Object@334dcfad
t3 acquiring lock on java.lang.Object@334dcfad
t3 acquired lock on java.lang.Object@334dcfad
t2 released lock on java.lang.Object@334dcfad
t2 released lock on java.lang.Object@1cf15b84
t2 finished execution.
t1 acquired lock on java.lang.Object@1cf15b84
t3 acquiring lock on java.lang.Object@5d0769dd
t1 released lock on java.lang.Object@1cf15b84
t3 acquired lock on java.lang.Object@5d0769dd
t1 released lock on java.lang.Object@5d0769dd
t1 finished execution.
t3 released lock on java.lang.Object@5d0769dd
t3 released lock on java.lang.Object@334dcfad
t3 finished execution.

申请资源的时间轴如下:
这里写图片描述

分析和上面的类似,由于给了线程之间足够多的时间过度去获得资源和利用资源,可以使得线程能够及时运行完并释放对象,从而避免了死锁的发生。

但是实际开发过程中我们很难预测到它们花费的具体时间,无法精确地通过它们的间隔来避免死锁的发生,但是可以通过其它的方法来避免死锁的发生。第一就是可以设置一个最长的等待时间,超过这个等待时间就释放资源而不是一直占用着不放,这个有点类似于时间片的功能;第二就是尽量不要互相占用对方的资源(当然这个也得根据需求来设计);再者就是尽量不要把整个对象全给锁住,可以锁住更小的区域,可以考虑使用Lock实现。

0 0