java中的死锁问题研究

江枫思渺然 提交于 2020-03-01 05:03:29

1.什么是死锁:

百度百科的定义:

所谓死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

java中死锁:某个任务在等待另一个任务释放锁,而后者又等待别的任务释放锁,这样一直下去,直到这个链上的任务又在等待第一个任务释放锁,这得到了一个任务之间的相互等待的连续循环,没有哪个线程能继续,这被称之为死锁。

2.经典的死锁现象---哲学家就餐问题

     由Edsger Dijkstra提出的哲学家就餐问题是一个经典的死锁例证。

哲学家就餐问题描述:

       设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间很多食物,每两个哲学家之间有一只筷子。当一个哲学家要就餐的时候,这个哲学家必须同时得到左边和右边的筷子,如果一个哲学家左边或右边已经有人在使用筷子,那么这个哲学家就必须等待,直到得到必需的筷子。

下面就是哲学家就餐问题的java编写的程序:

a.筷子类

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

任何两个哲学家都不能take()同一个筷子,另外,如果一根筷子已经被某个哲学家获得,那么另外一个哲学家就要wait(),直至这个筷子的当前持有者调用drop()使其可以使用为止。

 

b.哲学家的类

import java.util.concurrent.*;
import java.util.*;
import static mutex.conflict.Print.*;

public class Philosopher implements Runnable {
  private Chopstick left;
  private Chopstick right;
  private final int id;
  private final int ponderFactor;
  private Random rand = new Random(47);
  private void pause() throws InterruptedException {
    if(ponderFactor == 0) return;
    TimeUnit.MILLISECONDS.sleep(
      rand.nextInt(ponderFactor * 250));
  }
  public Philosopher(Chopstick left, Chopstick right,
    int ident, int ponder) {
    this.left = left;
    this.right = right;
    id = ident;
    ponderFactor = ponder;
  }
  public void run() {
    try {
      while(!Thread.interrupted()) {
        print(this + " " + "thinking");
        pause();
        // Philosopher becomes hungry
        print(this + " " + "grabbing right");
        right.take();
        print(this + " " + "grabbing left");
        left.take();
        print(this + " " + "eating");
        pause();
        right.drop();
        left.drop();
      }
    } catch(InterruptedException e) {
      print(this + " " + "exiting via interrupt");
    }
  }
  public String toString() { return "Philosopher " + id; }
} 

在哲学家的run()中,每个Philosopher只是不断的思考和吃饭。如果PonderFactor不为0,则pause()方法会休眠一段随机时间。通过这种方式,你将看到Philosopher会在思考上花掉一段随机化的时间,然后尝试或者获取(take())右边和左边的筷子,随后在吃饭再花掉随机化的时间,之后重复此过程。

c.测试程序,会发生死锁:

import java.util.concurrent.*;

public class DeadlockingDiningPhilosophers {
	public static void main(String[] args) throws Exception {
		int ponder = 1;
		if (args.length > 0)
			ponder = 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 Philosopher(sticks[i], sticks[(i + 1) % size], i,
					ponder));
		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();
	}
}

你会发现,如果Philosopher花在思考上的时间非常少,那么当他们想要进餐时,全都会在Chopstick上产生竞争,而死锁也就会更快的发生。

3.预防死锁

死锁必备条件

必须当一下四个条件同时满足,就会发生死锁:

  • 1) 互斥条件。
  • 2) 至少有一个任务他必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。   也就是说,要发生死锁,Philosopher必须拿着一个筷子,并且等待另外一根。
  • 3) 资源不能被任务抢占。
  • 4) 必须有循环等待。这时,一个任务等待其他任务所持有的资源,后者又在等待另外一个任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在我们的例子中,每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发送了循环等待。

        所以要解决死锁,只要破坏其中一个条件即可,在这个程序中,最容易的方法是破坏第四个条件。有这个条件的原因是每个Philosopher都试图用特定的顺序拿Chopstick:先右后左,正因如此,就可能会发生"每个人都拿着右边的Chopstick,并等待左边的Chopstick"的情况。这就是循环等待条件,然后如果最后一个Philosopher被初始化成先拿左边的Chopstick,在本例中,这就可以防止循环等待。这只是问题的解决办法之一,当然也可以通过破坏其他条件来防止死锁。

最后防止死锁的方法代码如下:

import java.util.concurrent.*;

public class FixedDiningPhilosophers {
  public static void main(String[] args) throws Exception {
    int ponder = 5;
    if(args.length > 0)
      ponder = 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++)
      if(i < (size-1))
        exec.execute(new Philosopher(
          sticks[i], sticks[i+1], i, ponder));
      else
        exec.execute(new Philosopher(
          sticks[0], sticks[i], i, ponder));
    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();
  }
}

       通过确保最后一个Philosopher先拿起和放下左边的Chopstick,我们可以移除死锁,从而使这个程序平滑的运行。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!