Java死锁问题

来源:互联网 发布:业务元数据 编辑:程序博客网 时间:2024/06/05 02:02
一般造成死锁必须同时满足如下4个条件:
  1,互斥条件:线程使用的资源必须至少有一个是不能共享的;
  2,请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其它线程持有的资源;
  3,非剥夺条件:分配资源不能从相应的线程中被强制剥夺;
  4,循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。

因为要产生死锁,这4个条件必须同时满足,所以要防止死锁的话,只需要破坏其中一个条件即可。

以生产者和消费者为例解决线程死锁:

在jdk中对于Object.wait有这样的一段解释:当前线程必须拥有此对象监视器。该线程放弃对此监视器的所有权并等待,直到其他线程通过调用notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。可见下面一种情况就可能出现:消费者1进入等待状态(此时资源锁已被放开),如果此时消费者2获取到了资源同步锁(没有人保证消费者1进入等待,下一个拿到锁的一定是生产者),消费者2判断没有资源也进入等待状态;此时生产者1生产了,并notify了消费者1,消费者1顺利地消费了,并执行notify操作,但此时消费者2却也因为资源而处于等待状态,从而唤醒了消费者2(消费者1本欲唤醒其他生产者),而此时并没有任何资源,导致了整个程序因为消费者2陷入无限的等待,形成了死锁。
经过以上分析,究其根本原因是:同时几个消费者或几个生产者处于等待状态,导致消费者可能唤醒的还是消费者,或者生产者唤醒的还是生产者。那么如果我们能够保证同时只有一个消费者处于wait状态(生产者同理),那就就能保证消费者唤醒的一定是生产者,从而能使整个任务顺利进行下去。下面是修改后的代码:
改进后的Consumer.java

packageCreatorAndConsumer;
 
publicclass Consumer implementsRunnable {
    /**
     * 线程资源
     */
    privatePlate plate;
 
    /**
     * 生产者锁:用于锁定同一时间只能有一个生产者进入生产临界区(如果同时又两个生产者进入临界区,那么很有可能其中一个生产者本想唤醒消费者却唤醒了生产者)
     */
    privatestatic Object consumerLocker = newObject();
 
    publicConsumer(Plate plate) {
        this.plate = plate;
    }
 
    @Override
    publicvoid run() {
        // 必须先获得生产者锁才能生产
        synchronized(consumerLocker) {
            synchronized(plate) {
                // 如果此时蛋的个数大于0,则等等
                while(plate.getEggNum() < 1) {
                    try{
                        // 这个细节需要注意,如果线程进入wait,那么其上的锁就会暂时得到释放,
                        // 不然其他线程也不能进行加锁,然后唤醒本线程
                        plate.wait();
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
 
                // 唤醒后,再次得到资源锁,且条件满足就可以放心地取蛋了
                plate.getEgg();
                plate.notify();
 
            }
        }
    }
}
packageCreatorAndConsumer;
 
/**
 * 生产者
 *
 * @author Martin
 *
 */
publicclass Creator implementsRunnable {
    /**
     * 线程资源
     */
    privatePlate plate;
 
    /**
     * 生产者锁:用于锁定同一时间只能有一个生产者进入生产临界区(如果同时又两个生产者进入临界区,那么很有可能其中一个生产者本想唤醒消费者却唤醒了生产者)
     */
    privatestatic Object creatorLocker = newObject();
 
    publicCreator(Plate plate) {
        this.plate = plate;
    }
 
    @Override
    publicvoid run() {
        //必须先获得生产者锁才能生产
        synchronized(creatorLocker) {
            synchronized(plate) {
                // 如果此时蛋的个数大于0,则等等
                while(plate.getEggNum() >= 5) {
                    try{
                        // 这个细节需要注意,如果线程进入wait,那么其上的锁就会暂时得到释放,
                        // 不然其他线程也不能进行加锁,然后唤醒本线程
                        plate.wait();
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
 
                // 唤醒后,再次得到资源锁,且条件满足就可以放心地生蛋啦
                Object egg = newObject();
                plate.addEgg(egg);
                plate.notify();
            }
        }
    }
}
测试类:
publicclass Tester {
    publicstatic void main(String[] args)
    {
        //共享资源
        Plate plate = newPlate();
         
        //添加生产者和消费者
        for(inti = 0; i < 100; i ++)
        {
            
            new Thread(new Creator(plate)).start();
            new Thread(new Consumer(plate)).start();
        }
    }
}


   改进说明:改进后的生产者和消费者分别加了生产者锁和消费者锁,分别用于锁定同一时间只能有一个消费者(生产者)进入生产临界区(如果同时又两个生产者进入临界区,那么很有可能其中一个生产者本想唤醒消费者却唤醒了生产者),总的来说就是一共三个锁:消费者锁、生产者锁、生产者和消费者共享的锁。
最终,写多线程的时候需要注意的是一个资源可能唤醒的是所有因该资源而等待的线程,因此消费者线程不一定唤醒的就是生产者线程也可能是消费者线程。

0 0
原创粉丝点击