我正在测试CompletableFuture
的工作方式。我对如何并行执行任务感兴趣:
try {
CompletableFuture one = CompletableFuture.runAsync(() -> {
throw new RuntimeException("error");
});
CompletableFuture two = CompletableFuture.runAsync(() -> System.out.println("2"));
CompletableFuture three = CompletableFuture.runAsync(() -> System.out.println("3"));
CompletableFuture all = CompletableFuture.allOf(one, two, three);
all.get();
} catch (InterruptedException e) {
System.out.println(e);
} catch (ExecutionException e) {
System.out.println(e);
}
在这种情况下,它们将全部执行。
1 。当其中一个异常时,是否有可能中断所有正在运行的线程?
2 。当此代码位于可从不同线程调用的类的方法中时,它是否是线程安全的?
答案 0 :(得分:2)
1。如果其中一个发生异常,是否可以中断所有正在运行的线程?
是的,有可能。所有线程都应该可以访问公共对象,该对象可以被其他线程更改和读取。例如可以是AtomicInteger
。参见以下示例:
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
public class Dates {
public static void main(String[] args) throws Exception {
try {
AtomicInteger excCounter = new AtomicInteger(0);
CompletableFuture one = CompletableFuture.runAsync(new ExcRunnable(excCounter));
CompletableFuture two = CompletableFuture.runAsync(new PrintRunnable("2", excCounter));
CompletableFuture three = CompletableFuture.runAsync(new PrintRunnable("3", excCounter));
CompletableFuture all = CompletableFuture.allOf(one, two, three);
all.get();
} catch (InterruptedException | ExecutionException e) {
System.out.println(e);
}
}
}
class ExcRunnable implements Runnable {
private final AtomicInteger excCounter;
public ExcRunnable(AtomicInteger excCounter) {
this.excCounter = excCounter;
}
@Override
public void run() {
Random random = new Random();
int millis = (int) (random.nextDouble() * 5000);
System.out.println("Wait " + millis);
Threads.sleep(450);
// Inform another threads that exc occurred
excCounter.incrementAndGet();
throw new RuntimeException("error");
}
}
class PrintRunnable implements Runnable {
private final String name;
private final AtomicInteger excCounter;
public PrintRunnable(String name, AtomicInteger excCounter) {
this.name = name;
this.excCounter = excCounter;
}
@Override
public void run() {
int counter = 10;
while (counter-- > 0 && excCounter.get() == 0) {
System.out.println(name);
Threads.sleep(450);
}
}
}
class Threads {
static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们有3个任务:两项打印其名称,另一项在一段时间后引发异常。在引发异常之前,计数器会递增计数,以通知其他任务其中之一失败,它们应该完成执行。打印作业正在检查此计数器,并在不满足条件的情况下完成其工作。当您评论excCounter.incrementAndGet();
行时,其他任务会完成其工作,而不会知道其中之一引发了异常。
- 当此代码位于可从不同线程调用的类的方法中时,它是否是线程安全的?
看看thread safety的定义。例如,假设打印任务使每个打印行的通用计数器递增。如果counter为原语int
,则不是线程安全的,因为可以替换counter值。但是,如果使用AtomicInteger
是线程安全的,因为AtomicInteger
是线程安全的。
答案 1 :(得分:1)
如果任何异步任务抛出异常,则all.get()将抛出异常。 这意味着,您可以取消catch子句中的所有CF。 但是,您的异步任务需要对中断友好,即定期检查中断标志或处理InterruptedException并尽早返回。
任务取消应始终使用中断机制处理
您提到的所有参考变量都是局部变量,因此无需担心线程安全性。局部变量始终是线程安全的。