Java,线程死锁?

Java, threads deadlocked?

我的一个朋友向我展示了他下面的代码,我认为这两个线程可能会死锁,因为它们在尝试获取不同变量的锁时可能会死锁:sb1sb2 .

当我 运行 代码时,它们似乎并没有陷入僵局,因为我能够看到输出:

A
B
second thread: AB
second thread: BA

代码如下:

public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();

        new Thread() {
            public void run() {
                synchronized (sb1) {
                    sb1.append("A");
                    synchronized (sb2) {
                        sb2.append("B");
                        System.out.println(sb1.toString());
                        System.out.println(sb2.toString());
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                synchronized (sb2) {
                    sb2.append("A");
                    synchronized (sb1) {
                        sb1.append("B");
                        System.out.println("second thread: " + sb1.toString());
                        System.out.println("second thread: " + sb2.toString());
                    }
                }
            }
        }.start();
    }

所以这两个线程会死锁吗?

没有出现死锁并不意味着不会发生死锁。

您正确地推断当两个线程试图以相反的顺序获取两个不同资源上的监视器时,可能 会发生死锁。

因此,这段代码可能产生死锁。

但是,如果两个线程中的任何一个设法先于另一个线程获取两个监视器,则不会发生死锁(这似乎在您的执行过程中发生)。

以下是死锁可能发生的方式:

  1. 线程 1 启动并在 sb1
  2. 上获取锁
  3. 线程二启动并在 sb2
  4. 上获取锁
  5. 线程一等待获取线程二拥有的 sb2 上的锁
  6. 线程二等待获取线程一sb1上的锁

由于没有线程会释放它的锁并且两个线程都在等待另一个线程,因此您会遇到死锁。

注意:正如Khelwood建议的那样,强制线程休眠可能会阻止其中一个线程先获取两个锁,从而产生死锁。

您发布的代码有一个潜在的死锁。如果运行成功,那就说明你走运了。

为了演示潜在的死锁,您可以操纵时间以确保发生死锁。

public static void main(String[] args) {
    final StringBuilder sb1 = new StringBuilder();
    final StringBuilder sb2 = new StringBuilder();

    new Thread() {
        public void run() {
            synchronized (sb1) {
                sb1.append("A");
                System.out.println("Thread 1 has sync sb1");
                try { Thread.sleep(700); }
                catch (InterruptedException e) { e.printStackTrace(); return; }
                System.out.println("Waiting for thread 1 to sync sb2");
                synchronized (sb2) {
                    sb2.append("B");
                    System.out.println(sb1.toString());
                    System.out.println(sb2.toString());
                }
            }
        }
    }.start();

    new Thread() {
        public void run() {
            try { Thread.sleep(500); }
            catch (InterruptedException e) { e.printStackTrace(); return; }
            synchronized (sb2) {
                System.out.println("Thread 2 has sync sb2");
                sb2.append("A");
                System.out.println("Waiting for thread 2 to sync sb1");
                synchronized (sb1) {
                    sb1.append("B");
                    System.out.println("second thread: " + sb1.toString());
                    System.out.println("second thread: " + sb2.toString());
                }
            }
        }
    }.start();
}

现在第一个线程肯定会在 sb1 上同步,第二个线程会在 sb2 上同步,然后就会出现死锁。

输出:

Thread 1 has sync sb1
Thread 2 has sync sb2
Waiting for thread 2 to sync sb1
Waiting for thread 1 to sync sb2

该代码完全解释了一个简单的死锁。

最简单的判断方式基本上是因为您的线程之间存在循环依赖关系。

这段代码很多时候会导致死锁。

是的,您的程序可能会以死锁结束。这是一个基于@khelwood 的回答的更动态的例子。它为实际的字符串附加添加了一些延迟,并在 while 循环中重复它。所以你可以看到发生了什么,程序迟早会以死锁结束。使用 ThreadMXBean.findDeadlockedThreads() 来识别死锁情况。

package stack43323164;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.List;

import java.lang.management.ThreadInfo;

public class HowToDemonstrateDeadlock {

    private static List<ThreadInfo> findDeadlocks() {
        ThreadMXBean tmxb = ManagementFactory.getThreadMXBean();
        long[] result = tmxb.findDeadlockedThreads();
        if (result == null)
            return java.util.Collections.emptyList();
        return java.util.Arrays.asList(tmxb.getThreadInfo(result, 2));
    }

    public static void main(String[] args) {

        final StringBuilder sb1 = new StringBuilder();
        final StringBuilder sb2 = new StringBuilder();

        long monitorDelay=1000L;
        //You can play with the delay times to modify the results
        long threadOneDelay=100L;
        long threadTwoDelay=100L;

        new Thread() {
            public void run() {
                try {
                    while (true) {
                        synchronized (sb1) {
                            sb1.append("A");
                            System.out.println("Thread 1 has sync sb1");
                            System.out.println("Waiting for thread 1 to sync sb2");
                            synchronized (sb2) {
                                sb2.append("B");
                                System.out.println(sb1.toString());
                                System.out.println(sb2.toString());
                            }
                        }
                        Thread.sleep(threadOneDelay);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread() {
            public void run() {
                try {
                    while (true) {
                        synchronized (sb2) {
                            System.out.println("Thread 2 has sync sb2");
                            sb2.append("A");
                            System.out.println("Waiting for thread 2 to sync sb1");
                            synchronized (sb1) {
                                sb1.append("B");
                                System.out.println("second thread: " + sb1.toString());
                                System.out.println("second thread: " + sb2.toString());
                            }
                        }
                        Thread.sleep(threadTwoDelay);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread() {
            public void run() {
                try {
                    while (true) {
                        List<ThreadInfo> deadlocks = findDeadlocks();
                        if (!deadlocks.isEmpty()) {
                            for (ThreadInfo i : deadlocks) {
                                System.out.println("Deadlock detected on thread " + i.getThreadId() + "\n" + i);
                            }
                            //Not a chance to solve the situation - boom
                            System.exit(1);
                        } else {
                            System.out.println("No deadlock so far.");
                        }
                        Thread.sleep(monitorDelay);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}