线程池浅析

2017-12-12  本文已影响14人  若尘0328
//java中线程池构造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;
如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。
keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用
TimeUnit
keepAliveTime的时间单位,和keepAliveTime搭配使用。
workQueue
workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能
threadFactory
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名
Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”
RejectedExecutionHandler(饱和策略)
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
(1)AbortPolicy:直接抛出异常,默认策略;
(2)CallerRunsPolicy:用调用者所在的线程来执行任务;
(3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
(4)DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
下面的代码演示自定义的饱和策略:

public class ThreadPoolDemo {
    private static CountDownLatch countDownLatch=new CountDownLatch(10);
    private static class Mywork implements Runnable{
        @Override
        public void run() {
            try {
                countDownLatch.await();
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        BlockingQueue<Runnable> quene=new LinkedBlockingQueue<>(5);
        RejectedExecutionHandler myReject=new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println(r+" is rejected");
            }
        };
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(2,4,1, TimeUnit.SECONDS,quene,myReject);
        //此处开启十个任务,但是最大线程池数(4)+阻塞队列大小(5)=9,所以肯定会有一个饱和任务
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(new Mywork());
            countDownLatch.countDown();
        }
        threadPoolExecutor.shutdown();
    }
}

程序输出结果:

com.thread.threadpool.ThreadPoolDemo$Mywork@3941a79c is rejected 

下面通过一张图解释线程池中任务的执行过程:

任务执行过程
提交任务给线程池threadPoolExecutor.execute(new Mywork());首先会判断当前线程数量是否小于核心线程数,是就创建线程执行任务,如果等于核心线程数量话再去判断阻塞队列是否已经满了,如果没有满就把任务加入到阻塞队列中,如果满了再去判断当前线程数量是否小于线程池的最大线程数量,是的话就再创建线程执行任务,如果这时当前当前线程数量等于最大线程数量的话就会执行饱和策略。上面代码我们自定义了自己的饱和策略,只是打印了一下执行饱和策略的线程的类。
整个执行流程可以参考下面的任务提交的逻辑:
任务提交逻辑
结合上面的图再来看源码:
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

那么任务被加入到线程池是如何被执行的呢?且听下次分解!

上一篇 下一篇

猜你喜欢

热点阅读