admin管理员组

文章数量:1598089

java线程池配置详解

ThreadPoolExecutor全参构造

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数说明

corePoolSize——核心线程数。

                            例如设置为5,默认情况下不会初始化线程,

                            当程序提交任务总数小于6个时,线程池在收到提交的线程时创建线程。

maximumPoolSize——最大线程数。例如设置为10,则线程池内最多创建10个线程存活。

keepAliveTime,unit——线程数多于核心线程数时,线程被回收前的空闲时间(值和单位)。

                                        例如:配置30秒,按以上5,10的示例,当线程池内的线程大于5个时,

                                        有现成空闲时间超过30秒则该线程会被回收。

workQueue——任务队列。当提交的任务需要排队处理时,任务会被放到该队列排队,等待处理。

                          什么时候入队列?当活跃线程数大于等于核心线程数时,操作入队列。

                          什么时候创建大于核心线程数的线程?

                          如果队列满了,调用创建线程方法,创建成功则当前任务开始在新线程中运行。

                          线程数达到上线了怎么办?执行拒绝策略。

threadFactory——线程工厂。

                             控制线程池中的线程对象的创建,创建线程时设置线程名,是否后台线程。

handler——拒绝策略。当任务既不能加入到线程执行,又不能加入到任务队列时,按此策略处理

                    默认采用中断策略,抛出异常。

                    即提交任务需要拒绝时,抛出RejectedExecutionException异常。

参数作用解释

        创建线程池后,提交任务时,当线程池内持有的线程数小于核心线程时,每提交一个任务创建一个线程,直到持有线程数等于核心线程数。

        当再次提交任务时,判断有没有空闲线程,有空闲线程则直接执行任务,没有空闲线程(活跃线程数等于核心线程数),则执行入队列操作,将任务添加到任务队列。

        添加任务队列时,如果队列未满,则写入队列等待执行。如果队列已满,则执行新建线程流程。

        新建线程时,首先判断当前线程数是否达到最大线程数,没有达到则新建线程,并在线程中执行这次操作提交的任务,原来队列中的任务继续排队。如果线程数已达到最大线程数,则执行拒绝策略

        当线程数大于核心线程数时,执行线程回收流程,当线程空闲时间达到配置的保持活跃时间(keepAliveTime,unit)时,该空闲线程被回收。 

        创建线程的工作由线程工厂完成,创建线程时,设定线程名,是否后台线程,线程优先级。

线程池使用

// 单线程线程池,核心线程和最大线程均为1,任务队列大小为Integer.MAX_VALUE
// 用于处理单线程排队任务
ExecutorService executor = Executors.newSingleThreadExecutor();

// 固定线程数线程池,核心线程和最大线程均为指定的线程数,任务队列大小为Integer.MAX_VALUE
// 用于处理多线程并发处理,还可以指定线程工厂
ExecutorService fixedExecutor = Executors.newFixedThreadPool(5);

// 缓存线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE,空闲时间为1分钟
// 提交任务时,如果有空闲线程则使用空闲线程执行任务,如果没有则创建一个线程执行任务
ExecutorService cachedExecutor = Executors.newCachedThreadPool();

// 计划任务线程池,周期性执行,间隔一定时间触发执行
ExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);

        虽然以上三个线程池可以满足大部分业务场景,但里面均没有合理的配置线程池必须的7个参数。实际使用时还要结合自己的场景来选择合适的线程池。

        首先需要考虑要解决的问题最多需要多少并发,实际场景有多少并发,自己的服务器又支持多少并发。一般情况下IO类型的可以多一些并发线程,毕竟IO过程消耗网卡资源,对CPU的消耗较小,CPU密集型任务一般不要超过CPU核数。

        然后需要选择合适的队列大小,尤其是对任务消耗时间未知,队列排队数量不可预估的情况下,必须制定合适的队列大小,并设置好拒绝策略。任务队列大小不限制的话默认为Integer.MAX_VALUE,这么多的Runnable对象,可能导致内存溢出。

        合适的拒绝策略,在任务数超过我们预期的情况时会发挥作用,默认的拒绝策略会抛出异常,指示出线程池有问题了。

线程池的作用

        最后简单说一下我理解的线程池的作用。

        1. 线程池解决了创建线程消耗资源的问题,主要是通过复用线程整体上减少了资源开销。详情见最后的测试结果(线程与任务耗时测试)。

        2. 解决了线程管理的问题,线程作为系统资源,是宝贵而又有限的,程序使用线程又有较高的技术要求,否则可能导致线程资源耗尽,内存溢出,CPU使用率高等问题。

        3. 降低了线程使用的门槛,简化了多线程使用方法,通过使用线程池,可以帮助我们更高效的编写简单,优雅,高效的程序。             

测试内容

线程与任务耗时测试-个人电脑测试结果:

线程池使用newCachedThreadPool,结果如下:

1万次任务提交,测试10次1万次线程启动测试10次
41ms~48ms304ms~357ms

测试代码如下:

    // 1万次任务
    private static void testTask(){
        long t1 = System.nanoTime();
        ExecutorService cachedExecutor = Executors.newCachedThreadPool();
        for(int i=0;i<10000;i++){
            cachedExecutor.submit(new Runnable() {
                @Override
                public void run() {
                }
            });
        }
        cachedExecutor.shutdown();
        long t2 = System.nanoTime();
        System.out.println("RunTask:"+(t2-t1));

    }
    //1万次线程
    private static void testThread(){
        long t1 = System.nanoTime();
        for(int i=0;i<10000;i++){
            new Thread().start();
        }
        long t2 = System.nanoTime();

        System.out.println("RunThread:"+(t2-t1));
    }

本文标签: 线程详解Java