简介
多线程锁定同一资源会造成死锁
线程池中的任务使用当前线程池也可能出现死锁
RxJava 或 Reactor 等现代流行库也可能出现死锁
死锁是两个或多个线程互相等待对方所拥有的资源的情形。举个例子,线程 A 等待 lock1,lock1 当前由线程 B 锁住,然而线程 B 也在等待由线程 A 锁住的 lock2。最坏情况下,应用程序将无限期冻结。让我给你看个具体例子。假设这里有个 Lumberjack(伐木工) 类,包含了两个装备的锁:
import com.google.common.collect.ImmutableList;
import lombok.RequiredArgsConstructor;
import java.util.concurrent.locks.Lock;
@RequiredArgsConstructor
class Lumberjack {
private final String name;
private final Lock accessoryOne;
private final Lock accessoryTwo;
void cut(Runnable work) {
try {
accessoryOne.lock();
try {
accessoryTwo.lock();
work.run();
} finally {
accessoryTwo.unlock();
}
} finally {
accessoryOne.unlock();
}
}
}
可以看到,有两种伐木工:先戴好安全帽然后再拿电锯的,另一种则相反。谨慎派(careful())伐木工先戴好安全帽,然后去拿电锯。狂野派伐木工(yolo())先拿电锯,然后找安全帽。让我们并发生成一些伐木工:
private List<Lumberjack> generate(int count, Supplier<Lumberjack> factory) {
return IntStream
.range(0, count)
.mapToObj(x -> factory.get())
.collect(toList());
}
generate()方法可以创建指定类型伐木工的集合。我们来生成一些谨慎派伐木工和狂野派伐木工。
private final Logging logging;
//...
List<Lumberjack> lumberjacks = new CopyOnWriteArrayList<>();
lumberjacks.addAll(generate(carefulLumberjacks, logging::careful));
lumberjacks.addAll(generate(yoloLumberjacks, logging::yolo));
最后,我们让这些伐木工开始工作:
IntStream
.range(0, howManyTrees)
.forEach(x -> {
Lumberjack roundRobinJack = lumberjacks.get(x % lumberjacks.size());
pool.submit(() -> {
log.debug("{} cuts down tree, {} left", roundRobinJack, latch.getCount());
roundRobinJack.cut(/* ... */);
});
});
这个循环让所有伐木工一个接一个(轮询方式)去砍树。实质上,我们向线程池(ExecutorService)提交了和树木数量(howManyTrees)相同个数的任务,并使用 CountDownLatch 来记录工作是否完成。
CountDownLatch latch = new CountDownLatch(howManyTrees);
IntStream
.range(0, howManyTrees)
.forEach(x -> {
pool.submit(() -> {
//...
roundRobinJack.cut(latch::countDown);
});
});
if (!latch.await(10, TimeUnit.SECONDS)) {
throw new TimeoutException("Cutting forest for too long");
}
其实想法很简单。我们让多个伐木工(Lumberjacks)通过多线程方式去竞争一个安全帽和一把电锯。完整代码如下:
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@RequiredArgsConstructor
class Forest implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(Forest.class);
private final ExecutorService pool;
private final Logging logging;
void cutTrees(int howManyTrees, int carefulLumberjacks, int yoloLumberjacks) throws InterruptedException, TimeoutException {
CountDownLatch latch = new CountDownLatch(howManyTrees);
List<Lumberjack> lumberjacks = new ArrayList<>();
lumberjacks.addAll(generate(carefulLumberjacks, logging::careful));
lumberjacks.addAll(generate(yoloLumberjacks, logging::yolo));
IntStream
.range(0, howManyTrees)
.forEach(x -> {
Lumberjack roundRobinJack = lumberjacks.get(x % lumberjacks.size());
pool.submit(() -> {
log.debug("{} cuts down tree, {} left", roundRobinJack, latch.getCount());
roundRobinJack.cut(latch::countDown);
});
});
if (!latch.await(10, TimeUnit.SECONDS)) {
throw new TimeoutException("Cutting forest for too long");
}
log.debug("Cut all trees");
}
private List<Lumberjack> generate(int count, Supplier<Lumberjack> factory) {
return IntStream
.range(0, count)
.mapToObj(x -> factory.get())
.collect(Collectors.toList());
}
@Override
public void close() {
pool.shutdownNow();
}
}
现在,让我们来看有趣的部分。如果我们只创建谨慎派伐木工(careful Lumberjacks),应用程序几乎瞬间运行完成,举个例子:
ExecutorService pool = Executors.newFixedThreadPool(10);
Logging logging = new Logging(new Names());
try (Forest forest = new Forest(pool, logging)) {
forest.cutTrees(10000, 10, 0);
} catch (TimeoutException e) {
log.warn("Working for too long", e);
}
作者:java菜
链接:https://www.jianshu.com/p/616909841551
共同學(xué)習(xí),寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章