并发主题

线程死锁研究

如果服务器当机或应用程序运行得如蜗牛,其中可能原因之一是被称为死锁线程问题。线程死锁就是一直处于堵塞BLOCKED 状态。

什么是死锁?两个线程A和B,那么死锁发生时,线程A块在等待线程B释放监视器锁,而线程B块,因为它也在等待线程A释放相同的监视器锁。

我们可以通过一些工具和技术可以帮助找出堵塞,如jVisualVM的,jstack和UNIX的kill命令。

以转账为案例,A帐号转钱到B账户,下面是账户代码:

public class Account {

  private final int number;

  private int balance;

  public Account(int number, int openingBalance) {
    this.number = number;
    this.balance = openingBalance;
  }

  public void withdraw(int amount) throws OverdrawnException {

    if (amount > balance) {
      throw new OverdrawnException();
    }

    balance -= amount;
  }

  public void deposit(int amount) {

    balance += amount;
  }

  public int getNumber() {
    return number;
  }

  public int getBalance() {
    return balance;
  }
}

下面是模拟死锁的调用代码:

public class DeadlockDemo {

  private static final int NUM_ACCOUNTS = 10;
  private static final int NUM_THREADS = 20;
  private static final int NUM_ITERATIONS = 100000;
  private static final int MAX_COLUMNS = 60;

  static final Random rnd = new Random();

  List<Account> accounts = new ArrayList<Account>();

  public static void main(String args[]) {

    DeadlockDemo demo = new DeadlockDemo();
    demo.setUp();
    demo.run();
  }

  void setUp() {

    for (int i = 0; i < NUM_ACCOUNTS; i++) {
      Account account = new Account(i, rnd.nextInt(1000));
      accounts.add(account);
    }
  }

  void run() {

    for (int i = 0; i < NUM_THREADS; i++) {
      new BadTransferOperation(i).start();
    }
  }

  class BadTransferOperation extends Thread {

    int threadNum;

    BadTransferOperation(int threadNum) {
      this.threadNum = threadNum;
    }

    @Override
    public void run() {

      for (int i = 0; i < NUM_ITERATIONS; i++) {

        Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));
        Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));
        int amount = rnd.nextInt(1000);

        if (!toAccount.equals(fromAccount)) {
          try {
            transfer(fromAccount, toAccount, amount);
            System.out.print(".");
          } catch (OverdrawnException e) {
            System.out.print("-");
          }

          printNewLine(i);
        }
      }
      // This will never get to here...
      System.out.println("Thread Complete: " + threadNum);
    }

    private void printNewLine(int columnNumber) {

      if (columnNumber % MAX_COLUMNS == 0) {
        System.out.print("\n");
      }
    }

    /**
     * The clue to spotting deadlocks is in the nested locking - synchronized keywords. Note that the locks DON'T
     * have to be next to each other to be nested.
     */
    private void transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {

      synchronized (fromAccount) {
        synchronized (toAccount) {
          fromAccount.withdraw(transferAmount);
          toAccount.deposit(transferAmount);
        }
      }
    }
  }
}

这是启动20个线程进行两个账户转账,注意transfer方法使用了synchronized

假设有两个线程A和B分别同时访问账户1和2,然后就会出现问题,当线程A锁住其1号的fromAccount,并试图锁定账号2的toAccount,。同时线程B锁住2号的fromAccount的,尝试锁定1号帐户的toAccount,。因此,线程A被线程B阻塞的,线程B被线程A阻塞 -陷入 一个僵局。

代码运行输出结果是:

 

下页

java多线程

Java同步或锁

Java性能调优