Java线程池学习笔记

2017-03-21  本文已影响0人  AmyXYC

引用自:http://blog.iluckymeeting.com/2018/01/06/JavaThreadPool/

线程池管理原则

Java线程池实现原理

Java通过线程池的工厂类Executors创建线程池,底层最终都是实例化了ThreadPoolExecutor来创建线程池

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

线程池中的核心线程数,即使线程当前是空闲状态,也会保留在线程池中不会被回收,除非设置了allowCoreThreadTimeOut参数

线程池中允许创建的最大线程池数量

线程最大空闲时间,如果线程池中的线程数量大于核心线程数量设置corePoolSize,则此参数起作用。当空闲线程等待任务到来的时间大于这个值,线程将被回收。

指的时keepAliveTime的单位

如果线程池中已经没有空闲线程了,而线程数量已经达到了maximumPoolSize时,意味着不能再创建新的线程,这时新的任务将被保存到阻塞队列中等待有空闲的线程出现。

创建线程的工厂类

当暂存任务的阻塞队列被填满时,新到来的任务既不能在阻塞队列里暂存等待处理,也没有空闲的线程来处理,需要要指定handler来处理这些任务

总结一下Java线程池的原理:
当有任务需要处理时,先在线程池中寻找空闲线程处理,如果没有空闲线程,而此时线程池中的线程数量没有达到corePoolSize,则创建新的线程处理任务;如果此时线程池中的线程数量已经达到了corePoolSize,则将任务放入阻塞队列中等待有线程空闲。当阻塞队列空间被用完,仍然没有空闲线程时,如果线程池中的线程数量小于等于maximumPoolSize,则创建新线程处理任务,这时如果出现了空闲线程,并且空闲时间大于keepAliveTime,则此线程会被回收。当线程池的线程数量达到maximumPoolSize,并且阻塞队列被填满,如果此时有新任务需要处理,新任务会被reject

线程池阻塞队列

等待被处理的任务将被暂存在阻塞队列中,任务需要实现Runnable接口,队列需要实现BlockingQueue接口。jdk提供了以下几种阻塞队列实现:

任务reject策略

当线程池中的线程数已达到最大允许线程数,并且没有空闲线程,阻塞队列也已填满,这时再有新的任务到达到时,新任务会被拒绝。jdk定义了RejectedExecutionHandler接口,并提供了几个不同的实现以提供多种任务丢弃策略。

Java线程池采用的默认策略是AbortPolicy,丢弃任务并抛异常,如果需要特别的处理策略,可以自己实现接口RejectedExecutionHandler。

Java线程池的常见使用

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

创建可以有nThreads个线程的线程池,核心线程数和最大线程数都是nThreads,阻塞队列是基于LinkedBlockingQueue的链表阻塞队列。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

创建一个线程数动态变化的线程池,被始线程数0,最大线程数是Integer.MAX_VALUE,当线程空闲超过60秒会被回收,阻塞队列是基于SynchronousQueue的,队列中并不存储元素,如果没有空闲的线程,直接创建新线程处理任务。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

创建一个线程池,当任务到达处理时间时会由线程池中的线程处理,核心线程数corePoolSize,最大线程数Integer.MAX_VALUE,当线程数超过核心线程数后,一旦线程空闲就会被回收,阻塞队列是基于DelayedWorkQueue,delay时间最小的会排在队头。

ThreadPoolExecutor实现

上面几个线程池的常见用法在底层都是通过ThreadPoolExecutor来实现的,在ThreadPoolExecutor实现中有一个重要的AtomicInteger类型的变量ctl,它存储了线程池的两个重要信息:线程数量、线程池状态

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

COUNT_BITS变量定义了ctl变量中有29位来表示线程数量,所以线程池的容量CAPACITY是(2^29)-1个。ctl中记录的线程数量,不仅仅是指处理激活状态的可以处理任务的线程,如果一个线程准备被回收,它已不能处理任务,但是并没有完成回收,那么这个线程也会被计算在线程数量之内。

下面介绍一下线程池状态

    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

ctl变量的高3位表示线程池状态,共有以下几种状态:

任务的执行

任务的执行是通过调用execute方法

void execute(Runnable command);

方法实现如下:

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))  //如果当前线程数小于核心线程数,则创建新线程处理任务
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {  //如果线程池状态是RUNNING,并且任务被成功存入阻塞队列
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))  //再次检查线程池状态,如果已不是RUNNING,则将任务由阻塞队列中移除,并将任务reject
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))  //如果线程池状态已不是RUNNING或者任务放入队列失败,则创建新线程处理任务,如果失败则reject任务
            reject(command);
上一篇 下一篇

猜你喜欢

热点阅读