ThreadPoolExecutor 与常用线程池

  • 为什么要使用线程池
  • ThreadPoolExecutor 线程池状态
  • ThreadPoolExecutor 线程池主要参数
  • ThreadPoolExecutor 线程池拒绝策略
  • Executors 与常用线程池
  • 使用案例

PS: ThreadPoolExecutor 源码分析请参考 Github

 

为什么要使用线程池


  1. 由线程池创建、调度、监控和销毁所有线程,控制线程数量,避免出现线程泄露,方便管理。

  2. 重复利用已创建的线程执行任务,不需要持续不断地重复创建和销毁线程,降低资源消耗。

  3. 直接从线程池空闲的线程中获取工作线程,立即执行任务,不需要每次都创建新的线程,从而提高程序的响应速度。

 

ThreadPoolExecutor 线程池状态


线程池状态主要有 RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED 五种,之间的关系如下图所示:

在这里插入图片描述
RUNNING:正常运行状态,接受新的任务(如果没有达到拒绝策略的条件)

SHUTDOWN:不接收新的任务,但是会继续运行正在线程中执行的任务和在队列中等待的任务

STOP:不接收新任务,也不会运行队列任务,并且中断正在运行的任务

TIDYING:所有任务都已经终止,workerCount 为0,当池状态为TIDYING时将会运行terminated()方法

TERMINATED:完全终止

 

ThreadPoolExecutor 线程池主要参数


构造一个 ThreadPoolExecutor 线程池主要需要以下六个参数

corePoolSize:核心线程数,表示通常情况下线程池中活跃的线程个数;

maximumPoolSize:线程池中可容纳的最大线程数;

keepAliveTime:此参数表示在线程数大于 corePoolSize 时,多出来的线程,空闲时间超过多少时会被销毁;

workQueue:存放任务的阻塞队列;

threadFactory:创建新线程的线程工厂;

handler:线程池饱和(任务过多)时的拒绝策略

 

ThreadPoolExecutor 线程池拒绝策略


此类中默认实现了以下四种拒绝策略(继承自 RejectedExecutionHandler 类,实现 rejectedExecution 方法):

CallerRunsPolicy:在调用者线程(而非线程池中的线程)中直接执行任务

AbortPolicy(默认):直接抛出 RejectedExecutionException 异常。程序中需要处理好此异常,不然会影响后续任务的执行。

DiscardPolicy:直接忽略任务,不执行任何操作。

DiscardOldestPolicy:丢弃任务队列中最老的任务,并将对此任务执行 execute。

 

Executors 与常用线程池


Executors 类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了 ExecutorService 接口,常用以下四种线程池(不包括 ForkJoinPool):

newCachedThreadPool: 线程数量没有限制(核心线程数设置为 0,其实有,最大限制为 Integer.MAX_VALUE),如果线程等待时间过长(默认为超过 60 秒),该空闲线程会自动终止。

newFixedThreadPool: 指定线程数量(核心线程数和最大线程数相同),在线程池没有任务可运行时,不会释放工作线程,还将占用一定的系统资源。

newSingleThreadPool: 只包含一个线程的线程池(核心线程数和最大线程数均为 1),保证任务顺序执行。

newScheduledThreadPool: 定长线程池,定时及周期性执行任务。可指定核心线程数,最大线程数为 Interger.MAX_VALUE。

注意:尽量使用 ThreadPoolExecutor 而不是 Executors 创建线程池。

Executors 创建的线程池对象的弊端如下:

  • FixedThreadPool 和 SingleThreadPool 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量请求,从而导致 OOM。

  • CacheThreadPool 和 ScheduledThreadPool 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量线程,从而导致 OOM。

 

使用案例


比较使用单线程和使用 (CPU + 1)个线程计算 10 次 1 + 2 + 3 + … + n 的时间消耗(简单测试),程序如下:

public class FixedThreadPoolAnalysis {

    public static void main(String[] args) {
        int n = 1000000000;
        long startTime = System.nanoTime();
        test.singleThread(n);
        long endTime = System.nanoTime();
        System.out.println("单线程计算 10 次 1 + 2 + ... + " + n + " 的时间消耗: " + (endTime - startTime) + "ns");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        startTime = System.nanoTime();
        test.multiThread(n);
        endTime = System.nanoTime();
        System.out.println("多线程计算 10 次 1 + 2 + ... + " + n + " 的时间消耗: " + (endTime - startTime) + "ns");
    }

    private static FixedThreadPoolAnalysis test = new FixedThreadPoolAnalysis();

    private void singleThread(int n) {
        long sum = 0L;
        for (int k = 0; k < 10; k++) {
            for (int i = 1; i <= n; i++)
                sum += i;
        }
        System.out.println(Thread.currentThread().getName() + " -> " + sum);
    }

    private void multiThread(int n) {
        int cpus = Runtime.getRuntime().availableProcessors();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(cpus + 1);
        int interval = n / (cpus + 1);
        for (int k = 0; k < 10; k++) {
            for (int i = 0; i < cpus; i++) {
                int left = i * interval + 1;
                int right = (i + 1) * interval;
                Runnable task = new Run(left, right);
                fixedThreadPool.execute(task);
            }
        }
        fixedThreadPool.shutdown();
    }

    class Run implements Runnable{
        private int left;
        private int right;
        Run(int left, int right) {
            this.left = left;
            this.right = right;
        }
        @Override
        public void run() {
            long sum = 0L;
            for (int i = left; i <= right; i++) {
                sum += i;
            }
            System.out.println(Thread.currentThread().getName() + " -> " + sum);
        }
    }
}

测试所用 CPU 为 8 个,结果如下:

n1001000010000001000000001000000000
单线程时间消耗(ns)72500332170023855700133883630012968975200
多线程时间消耗(ns)1146980016967900171614001122640019137600

结果显示,当 n 较小时,由于创建线程的时间消耗影响较大,使用多线程比使用单线程计算同一个任务的时间消耗更大。当 n 逐渐增大时,使用多线程的优势较明显。(此处不会出现任务数过多导致 OOM,所以为了简便,使用 Executors 创建 FixedThreadPool。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值