设为首页 加入收藏

TOP

使用Thread Pool不当引发的死锁(一)
2018-10-23 18:08:34 】 浏览:433
Tags:使用 Thread Pool 不当 引发

简介

  • 多线程锁定同一资源会造成死锁
  • 线程池中的任务使用当前线程池也可能出现死锁
  • 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();
        }
    }
}

每个 Lumberjack(伐木工)需要两件装备:helmet(安全帽) 和 chainsaw(电锯)。在他开始工作前,他必须拥有全部两件装备。我们通过如下方式创建伐木工们:

import lombok.RequiredArgsConstructor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@RequiredArgsConstructor
class Logging {
    private final Names names;
    private final Lock helmet = new ReentrantLock();
    private final Lock chainsaw = new ReentrantLock();
    Lumberjack careful() {
        return new Lumberjack(names.getRandomName(), helmet, chainsaw);
    }
    Lumberjack yolo() {
        return new Lumberjack(names.getRandomName(), chainsaw, helmet);
    }
}

可以看到,有两种伐木工:先戴好安全帽然后再拿电锯的,另一种则相反。谨慎派(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;
@RequiredAr
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇说说 MQ 之 Kafka(一) 下一篇InnoDB 存储引擎之索引和优化

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目