【Java 并发】死锁:Deadlock

Now you understand an object can have synchronized methods or other forms of locking that prevent tasks from accessing that object until the mutex is released. You’ve also learned that tasks can become blocked. Thus it’s possible for one task to get stuck waiting for another task, which in turn waits for another task, and so on, until the chain leads back to a task waiting on the first one. You get a continuous loop of tasks waiting on each other, and no one can move. This is called deadlock.

The real problem is when your program seems to be working fine but has the hidden potential to deadlock. In this case, you may get no indication that deadlocking is a possibility, so the flaw will be latent in your program until it unexpectedly happens to a customer (in a way that will almost certainly be difficult to reproduce).

不严谨的代码逻辑容易产生race condition,但是不一定总能触发死锁。发生死锁后其实并不好解决,因为死锁并不是总能复现。
以经典dining philosophers为例:先拿右边筷子再拿左边

 class Chopstick {
    public boolean taken =false;
    public synchronized void take() throws InterruptedException{
        while (taken){
            wait();
        }
        taken=true;
    }
    public synchronized void drop(){
        taken=false;
        notifyAll();
    }

}

import java.util.Random;
import java.util.concurrent.TimeUnit;
import static net.mindview.util.Print.*;

 class Philosophers implements Runnable{
    private Chopstick left;
    private Chopstick right;
    private final int id;
    private final int time;
    private Random rand = new Random(47);
    private void pause() throws InterruptedException {
        if(time == 0) return;
        TimeUnit.MILLISECONDS.sleep(rand.nextInt(time * 250));
    }
    public Philosophers(Chopstick left, Chopstick right, int ident, int t) {
        this.left = left;
        this.right = right;
        id = ident;
        time = t;
    }
    @Override
    public  void run() {
        try {
            while(!Thread.interrupted()) {
                print(this + " " + "thinking");
                pause();
// Philosopher becomes hungry
                    print(this + " " + "grabbing right   " +(id+1) % 5);
                    right.take();
                    print(this + " " + "grabbing left   "+ id);
                    left.take();
                if(right.taken&&left.taken){
                    print(this + " " + "eating");
                    pause();
                }
                synchronized (this){
                    right.drop();
                     System.out.println(this+" drop "+(id+1) % 5);
                }
                synchronized (this){
                    left.drop();
                    System.out.println(this+" drop "+id);
                }
            }
        } catch(InterruptedException e) {
            print(this + " " + "exiting via interrupt");
        }
    }
    public String toString() { return "Philosopher " + id; }
}

public class DeadlockingDiningPhilosophers {
    public static void main(String[] args) throws Exception{
        int t = 5;
        if(args.length > 0)
            t = Integer.parseInt(args[0]);
        int size = 5;
        if(args.length > 1)
            size = Integer.parseInt(args[1]);
        ExecutorService exec = Executors.newCachedThreadPool();
        Chopstick[] sticks = new Chopstick[size];
        for(int i = 0; i < size; i++)
            sticks[i] = new Chopstick();
            
        for(int i = 0; i < size; i++)
            exec.execute(new Philosophers(sticks[i], sticks[(i+1) % size], i, t));
            
        if(args.length == 3 && args[2].equals("timeout"))
            TimeUnit.SECONDS.sleep(5);
        else {
            System.out.println("Press ‘Enter’ to quit");
            System.in.read();
        }
        exec.shutdownNow();
    }
}

To repair the problem, you must understand that deadlock can occur if four conditions are simultaneously met:

  1. Mutual exclusion. At least one resource used by the tasks must not be shareable. In this case, a Chopstick can be used by only one Philosopher at a time.
  2. At least one task must be holding a resource and waiting to acquire a resource currently held by another task. That is, for deadlock to occur, a Philosopher must be holding one Chopstick and waiting for another one.
  3. A resource cannot be preemptively taken away from a task. Tasks only release resources as a normal event. Our Philosophers are polite and they don’t grab Chopsticks from other Philosophers.
  4. A circular wait can happen, whereby a task waits on a resource held by another task, which in turn is waiting on a resource held by another task, and so on, until one of the tasks is waiting on a resource held by the first task, thus gridlocking everything. In DeadlockingDiningPhilosophers.java, the circular wait happens because each Philosopher tries to get the right Chopstick first and then the left.

死锁发生的四个条件:
1、互斥。至少有一个持有资源是不可共享的。「上例中一个筷子只能同时被一个哲学家持有」
2、至少有一个任务正在持有资源,且等待另一个任务释放所持资源。「上例中一个哲学家必须持有一只筷子等待另一只」
3、任务无法抢占资源。「上例中哲学家无法从其他哲学家中抢走筷子」
4、闭环。相互请求资源行程闭环。「上例中哲学家总是先拿右边筷子,易形成闭环」
四个条件都满足即可产生死锁。
一般解决死锁的方法是破圈法,即打破资源请求的闭圈。

      ...
        for(int i = 0; i < size; i++)
            if(i < (size-1))
                exec.execute(new Philosophers(sticks[i], sticks[i+1], i,t ));
            else
                exec.execute(new Philosophers(sticks[0], sticks[i], i, t));

最后一个哲学家先拿左边的筷子,再拿右边筷子,解决死锁问题。

总结:当满足以上四种条件,产生死锁。解决死锁的办法是破圈法。由于死锁无法用Java包检测,只能靠程序员谨慎设计。

发布了27 篇原创文章 · 获赞 1 · 访问量 692

猜你喜欢

转载自blog.csdn.net/SUKI547/article/details/101537506