Java多线程环境检测系统中是否存在死锁和死锁恢复代码示例

ManagementFactory介绍

关于ManagementFactory:
ManagementFactory是一个可以获取JVM线程、内存、编译等信息的一个工厂类

    OperatingSystemMXBean system = ManagementFactory.getOperatingSystemMXBean();
        //相当于System.getProperty("os.name").
        System.out.println("系统名称:"+system.getName());
        //相当于System.getProperty("os.version").
        System.out.println("系统版本:"+system.getVersion());
              system.getSystemLoadAverage();
       // 返回最后一分钟的系统平均负载。 系统平均负载是排队到可用处理器的可运行实体的数量与在一段时间内平均运行在可用处理器上的可运行实体的数量之和。 计算负载平均值的方式是特定于操作系统的,但通常是阻尼时间相关的平均值。如果平均负载不可用,则返回负值。此方法旨在提供有关系统负载的提示,可能会被频繁查询。 在某些平台上,负载平均值可能不可用,因为实现此方法的成本很高。

在这里插入图片描述

死锁检测与恢复介绍

由于导致死锁的线程的不可控性(比如第三方软件启动的线程),因此死锁恢复的实际可操作性并不强:对死锁进行的故障恢复尝试可能是徒劳的(故障线程可无法响应中断)且有害的(可能导致活锁等问题)。 死锁的自动恢复有赖于线程的中断机制,其基本思想是:定义一个工作者线程DeadlockDetector专门用于死锁检测与恢复。该线程定期检测系统中是否存在死锁,若检测到死锁,则随机选取一个死锁线程并给其发送中断。该中断使得一个任意的死锁线程(目标线程)被Java虚拟机唤醒,从而使其抛出InterruptedException异常。这使得目标线程不再等待它本来永远也无法申请到的资源,从而破坏了死锁产生的必要条件中的“占用并等待资源”中的“等待资源”部分。
目标线程则通过对InterruptedException进行处理的方式来响应中断:目标线程捕获InterruptedException异常后将其已经持有的资源(锁)主动释放掉,这相当于破坏了死锁产生的必要条件中的“占用并等待资源”中的“占用资源”部分。接着,DeadlockDetector继续检测系统中是否仍然存在死锁,若存在,则继续选中一个任意的死锁线程并给其发送中断,直到系统中不再存在死锁。

用 ManagementFactory检测死锁:
需要用到getThreadMXBean()方法,这个方法返回 Java 虚拟机的线程系统的托管 bean。

代码

检测死锁的线程:

//工作者线程
public class DeadlockDetector extends Thread {
    
    
  static final ThreadMXBean tmb = ManagementFactory.getThreadMXBean();
  /**
   * 检测周期(单位为毫秒)
   */
  private final long monitorInterval;
//ManagementFactory是一个可以获取JVM线程、内存、编译等信息的一个工厂类

  public DeadlockDetector(long monitorInterval) {
    
    
    super("DeadLockDetector");
    setDaemon(true);
    this.monitorInterval = monitorInterval;
  }

  public DeadlockDetector() {
    
    
    this(2000);
  }

  public static ThreadInfo[] findDeadlockedThreads() {
    
    
    long[] ids = tmb.findDeadlockedThreads();
    return null == tmb.findDeadlockedThreads() ?
        new ThreadInfo[0] : tmb.getThreadInfo(ids);
  }

  public static Thread findThreadById(long threadId) {
    
    
    for (Thread thread : Thread.getAllStackTraces().keySet()) {
    
    
      if (thread.getId() == threadId) {
    
    
        return thread;
      }
    }
    return null;
  }

  public static boolean interruptThread(long threadID) {
    
    
    Thread thread = findThreadById(threadID);
    if (null != thread) {
    
    
      thread.interrupt();
      return true;
    }
    return false;
  }

  @Override
  public void run() {
    
    
    ThreadInfo[] threadInfoList;
    ThreadInfo ti;
    int i = 0;
    try {
    
    
      for (;;) {
    
    
        // 检测系统中是否存在死锁
        threadInfoList = DeadlockDetector.findDeadlockedThreads();
        if (threadInfoList.length > 0) {
    
    
          // 选取一个任意的死锁线程
          ti = threadInfoList[i++ % threadInfoList.length];
          Debug.error("Deadlock detected,trying to recover"
                      + " by interrupting%n thread(%d,%s)%n",
                  ti.getThreadId(),
                  ti.getThreadName());
          // 给选中的死锁线程发送中断
          DeadlockDetector.interruptThread(ti.getThreadId());
          continue;
        } else {
    
    
          Debug.info("No deadlock found!");
          i = 0;
        }
        Thread.sleep(monitorInterval);
      }// for循环结束
    } catch (InterruptedException e) {
    
    
      // 什么也不做
      ;
    }
  }
}

DeadlockDetector是通过java.lang.management.ThreadMXBean.findDeadlockedThreads()调用来实现死锁检测的。ThreadMXBean.findDeadlockedThreads()能够返回一组死锁线程的线程编号。ThreadMXBean类是JMX(Java Management Extension)API的一部分,因此其提供的功能也可以通过jconsole、jvisualvm手工调用。
注意:通过ReentrantLock.lock()申请显式锁的,因此它无法响应中断,也就无法支持死锁的自动恢复。

发生死锁的统一抽象类:

/**
 * 对哲学家进行抽象
 *
 * @author Viscent Huang
 */
public abstract class AbstractPhilosopher extends Thread implements Philosopher {
    
    
  protected final int id;
  protected final Chopstick left;
  protected final Chopstick right;

  public AbstractPhilosopher(int id, Chopstick left, Chopstick right) {
    
    
    super("Philosopher-" + id);
    this.id = id;
    this.left = left;
    this.right = right;
  }

  @Override
  public void run() {
    
    
    for (;;) {
    
    
      think();
      eat();
    }
  }

  /*
   * @see io.github.viscent.mtia.ch7.diningphilosophers.Philosopher#eat()
   */
  @Override
  public abstract void eat();

  protected void doEat() {
    
    
    Debug.info("%s is eating...%n", this);
    Tools.randomPause(10);
  }

  /*
   * @see io.github.viscent.mtia.ch7.diningphilosophers.Philosopher#think()
   */
  @Override
  public void think() {
    
    
    Debug.info("%s is thinking...%n", this);
    Tools.randomPause(10);
  }

  @Override
  public String toString() {
    
    
    return "Philosopher-" + id;
  }
}

`

公共资源类


/**
 * 筷子
 *
 * @author Viscent Huang
 */
public class Chopstick {
    
    
  public final int id;
  private Status status = Status.PUT_DOWN;

  public static enum Status {
    
    
    PICKED_UP,
    PUT_DOWN
  }

  public Chopstick(int id) {
    
    
    super();
    this.id = id;
  }

  public void pickUp() {
    
    
    status = Status.PICKED_UP;
  }

  public void putDown() {
    
    
    status = Status.PUT_DOWN;
  }

  public Status getStatus() {
    
    
    return status;
  }

  @Override
  public String toString() {
    
    
    return "chopstick-" + id;
  }
}

导致死锁的模型


``java

public class BuggyLckBasedPhilosopher extends AbstractPhilosopher {
    
    
  /**
   * 为确保每个Chopstick实例有且仅有一个显式锁(而不重复创建)与之对应,<br>
   * 这里的map必须采用static修饰!
   */
  protected final static ConcurrentMap<Chopstick, ReentrantLock> LOCK_MAP;
  static {
    
    
    LOCK_MAP = new ConcurrentHashMap<Chopstick, ReentrantLock>();
  }

  public BuggyLckBasedPhilosopher(int id, Chopstick left, Chopstick right) {
    
    
    super(id, left, right);
    // 每个筷子对应一个(唯一)锁实例
    LOCK_MAP.putIfAbsent(left, new ReentrantLock());
    LOCK_MAP.putIfAbsent(right, new ReentrantLock());
  }

  @Override
  public void eat() {
    
    
    // 先后拿起左手边和右手边的筷子
    if (pickUpChopstick(left) && pickUpChopstick(right)) {
    
    
      // 同时拿起两根筷子的时候才能够吃饭
      try{
    
    
        doEat();
      } finally {
    
    
        // 放下筷子
        putDownChopsticks(right, left);
      }
    }
  }

//  @SuppressFBWarnings(value = "UL_UNRELEASED_LOCK",
//      justification = "筷子对应的锁由应用自身保障总是能够被释放")
  protected boolean pickUpChopstick(Chopstick chopstick) {
    
    
    final ReentrantLock lock = LOCK_MAP.get(chopstick);

    try {
    
    
      lock.lock();
      Debug.info("%s is picking up %s on his %s...%n",
          this, chopstick, chopstick == left ? "left" : "right");

      chopstick.pickUp();
    }
    catch (Exception e) {
    
    
      // 不大可能走到这里
      e.printStackTrace();
      lock.unlock();
      return false;
    }
    return true;
  }

  private void putDownChopsticks(Chopstick chopstick1, Chopstick chopstick2) {
    
    
    try {
    
    
      putDownChopstick(chopstick1);
    } finally {
    
    
      putDownChopstick(chopstick2);
    }
  }

  protected void putDownChopstick(Chopstick chopstick) {
    
    
    final ReentrantLock lock = LOCK_MAP.get(chopstick);
    try {
    
    
      Debug.info("%s is putting down %s on his %s...%n",
          this, chopstick, chopstick == left ? "left" : "right");
      chopstick.putDown();
    } finally {
    
    
      lock.unlock();
    }
  }
}

模型实现类


public class RecoverablePhilosopher extends BuggyLckBasedPhilosopher {
    
    

  public RecoverablePhilosopher(int id, Chopstick left, Chopstick right) {
    
    
    super(id, left, right);
  }

  @Override
  protected boolean pickUpChopstick(Chopstick chopstick) {
    
    
    final ReentrantLock lock = LOCK_MAP.get(chopstick);
    try {
    
    
      lock.lockInterruptibly();
      //这里,pickUpChopstick方法在捕获到lock.lockInterruptibly()抛出的InterruptedException后,主动将当前线程已持有的锁释放掉(即放下当前哲学家已持有的筷子)。利用这个改造后的哲学家模型,我们就可以再现死锁的自动恢复的效果 

    } catch (InterruptedException e) {
    
    
      // 使当前线程释放其已持有的锁
      Debug.info("%s detected interrupt.", Thread.currentThread().getName());
      Chopstick theOtherChopstick = chopstick == left ? right : left;
      theOtherChopstick.putDown();
      LOCK_MAP.get(theOtherChopstick).unlock();
      return false;
    }
    try {
    
    
      Debug.info(
          "%s is picking up %s on his %s...%n",
          this, chopstick, chopstick == left ? "left" : "right");

      chopstick.pickUp();
    } catch (Exception e) {
    
    
      // 不大可能走到这里
      e.printStackTrace();
      lock.unlock();
      return false;
    }
    return true;
  }
}

模拟死锁的程序类


public class DiningPhilosopherProblem {
    
    

  public static void main(String[] args) throws Exception {
    
    
    int numOfPhilosopers;
    numOfPhilosopers = args.length > 0 ? Integer.valueOf(args[0]) : 2;
    // 创建筷子
    Chopstick[] chopsticks = new Chopstick[numOfPhilosopers];
    for (int i = 0; i < numOfPhilosopers; i++) {
    
    
      chopsticks[i] = new Chopstick(i);
    }

    String philosopherImplClassName = System.getProperty("x.philo.impl");
    if (null == philosopherImplClassName) {
    
    
      philosopherImplClassName = "DeadlockingPhilosopher";
    }
    Debug.info("Using %s as implementation.", philosopherImplClassName);
    for (int i = 0; i < numOfPhilosopers; i++) {
    
    
      // 创建哲学家
      createPhilosopher(philosopherImplClassName, i, chopsticks);
    }
  }

  private static void createPhilosopher(String philosopherImplClassName,
      int id, Chopstick[] chopsticks) throws Exception {
    
    

    int numOfPhilosopers = chopsticks.length;
    @SuppressWarnings("unchecked")
    Class<Philosopher> philosopherClass = (Class<Philosopher>) Class
        .forName(DiningPhilosopherProblem.class.getPackage().getName() + "."
            + philosopherImplClassName);
    Constructor<Philosopher> constructor = philosopherClass.getConstructor(
        int.class, Chopstick.class, Chopstick.class);
    Philosopher philosopher = constructor.newInstance(id, chopsticks[id],
        chopsticks[(id + 1)
            % numOfPhilosopers]);
    philosopher.start();
  }
}

结果:
在这里插入图片描述
死锁的自动恢复有赖于死锁的线程能够响应中断。以上面RecoverablePhilosopher为例,如果我们在代码开发与维护过程中能够意识到它是可能导致死锁的,那么我们应该采取的措施是规避死锁(防患未然)而不是使其支持死锁的自动恢复(为亡羊补牢做准备);相反,如果我们未能事先意识到死锁这个问题,那么这个类的相关方法可能根本无法响应中断,或者能够响应中断但是其响应的结果却未必是DeadlockDetector所期望的——释放其已持有的资源。 其次,自动恢复尝试可能导致新的问题。例如,如果RecoverablePhilosopher(对中断的响应方式是仅仅保留中断标记而并不释放其已持有的资源,即RecoverablePhilosopher.pickUpChopstick方法对InterruptedException异常的处理逻辑仅仅是调用Thread.currentThread().interrupt()以保留中断标记,那么尝试对这样的死锁线程进行恢复非但不能达到预期效果,反而会造成相应线程一直在尝试申请锁而一直无法申请成功,即产生活锁!

猜你喜欢

转载自blog.csdn.net/qq_41358574/article/details/121908722