线程池的常见用法

最近的面试被频繁问到, 这里简单梳理下:

先说java.uitl.concurrent.ThreadPoolExecutor类, 这是线程池最核心的一个类, 提供了4个构造方法, 前三个到最后都是对第四个进行调用;

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

构造器里的几个核心参数的含义:

corePoolSize : 核心池的大小, 创建了线程池后默认不立即创建线程, 待到有任务来了再创建线程去执行, 直到线程数达到corePoolSize。 再来任务便加入阻塞队列, 队列满了之后再扩容, 直到最大线程数maximumPoolSize;再来任务的话就抛出任务拒绝的异常。
maximumPoolSize : 池子最大线程数
keepAliveTime : 线程过期时间, 无任务状态下线程最多保持多长时间终止
TimeUnit unit : 时间单位(分/秒/毫秒)
workQueue : 阻塞队列

 

简单测试具体的用法:

public static void main(String[] args) {
		ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5));
         
        for(int i=0; i<15; i++){
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
            executor.getQueue().size()+",已执行的任务数目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
	}

任务类MyTask:

class MyTask implements Runnable {
    private int taskNum;
    public MyTask(int num) {
        this.taskNum = num;
    }
    @Override
    public void run() {
        System.out.println("正在执行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"执行完毕");
    }
}

执行的结果:

正在执行task 0
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行的任务数目:0
正在执行task 1
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行的任务数目:0
正在执行task 2
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行的任务数目:0
正在执行task 3
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 4
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 10
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行的任务数目:0
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 12
正在执行task 11
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行的任务数目:0
正在执行task 13
正在执行task 14
task 0执行完毕
正在执行task 5
task 2执行完毕
task 1执行完毕
正在执行task 6
正在执行task 7
task 3执行完毕
正在执行task 8
task 4执行完毕
task 10执行完毕
正在执行task 9
task 14执行完毕
task 13执行完毕
task 12执行完毕
task 11执行完毕
task 5执行完毕
task 6执行完毕
task 7执行完毕
task 8执行完毕
task 9执行完毕

综上可以看出:

一开始线程池里是没有线程的,任务来了之后才开始创建线程,直到线程数达到核心池的大小(5个)。这时候再来任务的时候池子不会直接扩容,而是将任务加入阻塞队列,直到队列(5个)也满了再去扩容线程,直到扩到池子最大线程数(10个)终止。这时候池子线程数达到上限10个,队列阻塞也达到最大值5个,再来新的任务的话,就要报错了(抛出任务拒绝异常)。

 

在实际应用的时候,线程池的大小具体配成多少比较合适?

网上搜了下,比较一致的说法是:

线程数=CPU可用核心数/(1-阻塞系数),其中阻塞系数的取值在0和1之间。

简单来记就是:

IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)

计算密集型=Ncpu(常出现于线程中:复杂算法)

## Ncpu就是指具体的几核的CPU,像我这4核的配corePoolSize 参数的时候,配成4 - 8基本就是ok的!

文章参考:

https://www.cnblogs.com/dolphin0520/p/3932921.html

https://www.cnblogs.com/dennyzhangdd/p/6909771.html?utm_source=itdadao&utm_medium=referral

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值