对Completablefuture的使用不当,在异步请求中又嵌入了异步调用,当依赖服务变慢或请求量变大时,future会相互等待产生死锁。
在Java中,使用CompletableFuture
时出现死锁的情况可能与不当的线程同步和锁定有关。下面是一个示例程序,演示了如何使用CompletableFuture
造成死锁,并输出类似于您提供的死锁日志。
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDeadlock {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) throws Exception {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " acquired lock1");
// Adding a sleep to ensure both threads try to acquire the second lock simultaneously
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " acquired lock2");
}
}
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " acquired lock2");
// Adding a sleep to ensure both threads try to acquire the first lock simultaneously
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " acquired lock1");
}
}
});
// Wait for both futures to complete
future1.get();
future2.get();
}
}
解释
CompletableFuture.runAsync
在不同的线程中异步执行任务。Thread-0
持有lock1
,尝试获取lock2
。Thread-1
持有lock2
,尝试获取lock1
。- 由于两个线程都在等待对方持有的锁,这就造成了死锁。
解决方法
要避免死锁,可以重新设计代码,确保锁的获取顺序一致,或者尽量减少对共享资源的锁定。
要还原一个使用CompletableFuture
的不当用法,在异步请求中又嵌入异步调用,导致在依赖服务变慢或请求量变大时产生死锁的场景,可以通过创建两个相互依赖的CompletableFuture
任务来实现。下面是一个示例代码:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureDeadlockExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Create two CompletableFuture objects that depend on each other
CompletableFuture<Void> future1 = new CompletableFuture<>();
CompletableFuture<Void> future2 = new CompletableFuture<>();
// Run the first async task
CompletableFuture.runAsync(() -> {
try {
// Simulate a slow dependency
Thread.sleep(100);
System.out.println("Future1 waiting for Future2");
// Future1 waits for Future2
future2.get();
System.out.println("Future1 completed");
future1.complete(null);
} catch (InterruptedException | ExecutionException e) {
future1.completeExceptionally(e);
}
});
// Run the second async task
CompletableFuture.runAsync(() -> {
try {
// Simulate a slow dependency
Thread.sleep(100);
System.out.println("Future2 waiting for Future1");
// Future2 waits for Future1
future1.get();
System.out.println("Future2 completed");
future2.complete(null);
} catch (InterruptedException | ExecutionException e) {
future2.completeExceptionally(e);
}
});
// Wait for both futures to complete
try {
CompletableFuture.allOf(future1, future2).join();
} catch (Exception e) {
System.out.println("Caught exception: " + e);
}
System.out.println("Done");
}
}
解释
- 创建两个
CompletableFuture
对象future1
和future2
。 - 第一个异步任务尝试等待
future2
完成,然后完成future1
。 - 第二个异步任务尝试等待
future1
完成,然后完成future2
。 - 由于
future1
和future2
互相等待对方完成,就会出现死锁。
运行结果
当您运行此程序时,您会发现程序被永久卡住,因为两个CompletableFuture
互相等待,导致死锁。
解决方案
避免这种情况的方法是重新设计异步任务的依赖关系,确保不会出现互相等待的情况。可以通过以下方式解决:
- 避免在异步任务中等待其他异步任务的结果。
- 使用
thenCompose
或thenCombine
等方法来链式处理异步任务,而不是手动等待它们完成。
例如,使用thenCompose
来避免死锁:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureSolution {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(100);
System.out.println("Future1 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
CompletableFuture<Void> future2 = future1.thenRunAsync(() -> {
try {
Thread.sleep(100);
System.out.println("Future2 completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// Wait for both futures to complete
CompletableFuture.allOf(future1, future2).join();
System.out.println("Done");
}
}
在这个解决方案中,future2
依赖于future1
,并且使用thenRunAsync
来避免直接等待,从而避免了死锁。
如何发现这一问题:
JStack
是一个分析 Java 应用程序线程状态的有用工具,特别是在诊断死锁问题时非常有用。以下是如何使用 JStack
分析死锁问题的步骤:
步骤 1:获取 Java 进程 ID
首先,需要获取正在运行的 Java 应用程序的进程 ID(PID)。可以使用以下命令找到 PID:
jps -l
这个命令将列出所有运行的 Java 进程及其 PID。例如:
12345 com.example.MyApplication
这里 12345
是 Java 应用程序的 PID。
步骤 2:生成线程转储
使用 jstack
命令生成指定 Java 进程的线程转储:
jstack -l 12345 > thread_dump.txt
这将生成一个包含所有线程状态的文件 thread_dump.txt
。
步骤 3:分析线程转储
打开生成的 thread_dump.txt
文件,查找死锁信息。通常,如果存在死锁,jstack
输出中会有类似以下的信息:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f9c00000008 (object 0x00007f9c00003010, a java.lang.Object),
which is held by "Thread-2"
"Thread-2":
waiting to lock monitor 0x00007f9c00000010 (object 0x00007f9c00003020, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at MyClass.methodA(MyClass.java:10)
- waiting to lock <0x00007f9c00003010> (a java.lang.Object)
- locked <0x00007f9c00003020> (a java.lang.Object)
at MyClass.run(MyClass.java:20)
"Thread-2":
at MyClass.methodB(MyClass.java:15)
- waiting to lock <0x00007f9c00003020> (a java.lang.Object)
- locked <0x00007f9c00003010> (a java.lang.Object)
at MyClass.run(MyClass.java:25)
Leave a Reply