Java线程池

来源:互联网 发布:linux 编译动态库 编辑:程序博客网 时间:2024/06/10 15:10

    • ThreadPoolExecutor
    • 线程池实现原理
      • 线程池状态
    • 示例
    • 合理配置线程池大小

ThreadPoolExecutor

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.corePoolSize = corePoolSize;    this.maximumPoolSize = maximumPoolSize;    this.workQueue = workQueue;    this.keepAliveTime = unit.toNanos(keepAliveTime);    this.threadFactory = threadFactory;    this.handler = handler;}

构造方法参数:

  • corePoolSize:核心池大小。默认情况下,创建一个线程池初始线程个数为0,当有任务的时候才会去创建线程,当创建的线程数达到corePoolSize后,就会把新到达的任务放到阻塞队列中。除了调了prestartAllCoreThreads()或者prestartCoreThread()方法,这两个方法会在初始化的时候就创建所有核心线程或1个核心线程。
  • maximumPoolSize:最大线程数。表示线程池最多能创建的线程数量。
  • keepAliveTime:线程空闲时间。默认情况下只有当线程数超过了corePoolSize这个参数才会起作用,一个线程的空闲时间如果超过了keepAliveTime,就会被销毁,直到线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean),keepAliveTime参数会一直生效,直到线程池数量为0.
  • unit:keepAliveTime的时间单位。在TimeUnit类中有7种静态属性。
  • workQueue:阻塞队列。用于存储等待执行的任务。一般有
ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;


* threadFactory:线程工厂。用于创建线程。
* handler:线程池拒绝处理任务时策略。一般有:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

线程池实现原理

线程池状态

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

这里有个很关键的字段ctl,因为这个变量控制了线程状态和线程数量,且具有原子性。很神奇,看看源码注释是怎么说的:

/*** The main pool control state, ctl, is an atomic integer packing* two conceptual fields*   workerCount, indicating the effective number of threads*   runState,    indicating whether running, shutting down etc** In order to pack them into one int, we limit workerCount to* (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2* billion) otherwise representable. If this is ever an issue in* the future, the variable can be changed to be an AtomicLong,* and the shift/mask constants below adjusted. But until the need* arises, this code is a bit faster and simpler using an int.*/

由于一个int类型是由32位的二进制数构成的,这里很巧妙的利用高3位表示runState,用低29位表示workerCount。这段代码让我感觉这个类非常的精细。解释称2^29 - 1 大概5亿。如果之后不够的话可以将int类型改成long,但是现在使用int会比较简单有效。
以下将介绍几个ThreadPoolExecutor类中常见且相关的几个方法:

private static int ctlOf(int rs, int wc) { return rs | wc; }

其中rs就是高三位的runState,wc就是低29位的workerCount。

private static int runStateOf(int c)     { return c & ~CAPACITY; }private static int workerCountOf(int c)  { return c & CAPACITY; }

c就是前面ctl的值。这两个方法分别用来获取runState和workerCount。
其中CAPACITY的定义:

private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

可以看出CAPACITY其实就是workerCount的最大值。

/*** The runState provides the main lifecyle control, taking on values:**   RUNNING:  Accept new tasks and process queued tasks*   SHUTDOWN: Don't accept new tasks, but process queued tasks*   STOP:     Don't accept new tasks, don't process queued tasks,*             and interrupt in-progress tasks*   TIDYING:  All tasks have terminated, workerCount is zero,*             the thread transitioning to state TIDYING*             will run the terminated() hook method*   TERMINATED: terminated() has completed*/
  • RUNNING:可以接收新任务和处理阻塞列表中的任务
  • SHUTDOWN:不能接收新任务,但是会继续处理阻塞列表中的任务
  • STOP:不能接收新任务且也不会再处理阻塞列表中的任务
  • TIDYING:所有任务都结束了,workCount为0,并且会调用terminated()
  • TERMINATED:terminated()调用结束
// runState is stored in the high-order bitsprivate 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;

代码中会有rs < SHUTDOWN 的判断,其实就是指RUNNING

示例

/** * Created by cxx on 2017/6/16. */public class TreadPoolTest {    public static void main(String[] args) {        ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,                new ArrayBlockingQueue<Runnable>(5),new ThreadPoolExecutor.DiscardPolicy());        System.out.println("启动后线程数量:"+pool.getPoolSize());        for(int i=0;i<20;i++){            for (;;){                if (pool.getQueue().size() <5 || pool.getPoolSize() < 10){                    Task myTask = new Task(i);                    pool.execute(myTask);                    System.out.println("线程池数:"+pool.getPoolSize()+",队列数:"+                            pool.getQueue().size());                    break;                }            }        }        pool.shutdown();    }}class Task implements Runnable {    private int taskNum;    public Task(int num) {        this.taskNum = num;    }    @Override    public void run() {        System.out.println("开始执行 "+taskNum);        try {            Thread.currentThread().sleep(3000);        } catch (InterruptedException e) {            System.out.println("被中断啦。。。。。。。。。。");        }        System.out.println("task "+taskNum+"执行完毕");    }}

运行结果

启动后线程数量:0线程池数:1,队列数:0开始执行 0线程池数:2,队列数:0开始执行 1线程池数:3,队列数:0开始执行 2线程池数:4,队列数:0开始执行 3线程池数:5,队列数:0开始执行 4线程池数:5,队列数:1线程池数:5,队列数:2线程池数:5,队列数:3线程池数:5,队列数:4线程池数:5,队列数:5线程池数:6,队列数:5开始执行 10线程池数:7,队列数:5开始执行 11线程池数:8,队列数:5开始执行 12线程池数:9,队列数:5开始执行 13线程池数:10,队列数:5开始执行 14task 0执行完毕task 4执行完毕开始执行 5task 3执行完毕task 1执行完毕开始执行 8task 2执行完毕开始执行 9task 12执行完毕开始执行 7task 11执行完毕task 10执行完毕开始执行 6task 14执行完毕task 13执行完毕线程池数:10,队列数:1开始执行 15开始执行 16线程池数:10,队列数:1线程池数:10,队列数:1开始执行 17开始执行 18线程池数:10,队列数:1线程池数:10,队列数:1开始执行 19task 8执行完毕task 9执行完毕task 5执行完毕task 7执行完毕task 6执行完毕task 15执行完毕task 16执行完毕task 17执行完毕task 19执行完毕task 18执行完毕Process finished with exit code 0

看运行结果可以看到,当核心数达到corePoolSize,就会往workQueue添加,workQueue满了之后,就会创建新的线程直到线程数达到maximumPoolSize。
在嵌套循环中判断线程和队列是否饱和,如果饱和就等会在添加。(现在添加的话会被丢弃,因为策略配置的是丢弃并且不抛异常)

合理配置线程池大小

在javadoc中并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态类:

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

固定线程数线程池,核心和最大线程数都一样。由于keepAliveTime为0,所以一旦线程启动后除非被shutdown,否则会一直等待新任务。队列是一个没有大小限制的队列,你唯一要担心的是内存够不够用。

public static ExecutorService newSingleThreadExecutor() {    return new FinalizableDelegatedExecutorService        (new ThreadPoolExecutor(1, 1,                                0L, TimeUnit.MILLISECONDS,                                new LinkedBlockingQueue<Runnable>()));}

相当于固定线程数线程池的线程数为1.但是我还注意到这里返回的是FinalizableDelegatedExecutorService类。好奇心让我点开了这个类:

static class FinalizableDelegatedExecutorService    extends DelegatedExecutorService {    FinalizableDelegatedExecutorService(ExecutorService executor) {        super(executor);    }    protected void finalize() {        super.shutdown();    }}

是一个Executors的内部类,构造函数调用父类的构造方法,覆盖了finalize方法,用于垃圾回收时触发shutdown。(这一点暂时不去深究,为什么要去这么做)。在点开父类:

static class DelegatedExecutorService extends AbstractExecutorService {    private final ExecutorService e;    DelegatedExecutorService(ExecutorService executor) { e = executor; }    public void execute(Runnable command) { e.execute(command); }    public void shutdown() { e.shutdown(); }    public List<Runnable> shutdownNow() { return e.shutdownNow(); }    public boolean isShutdown() { return e.isShutdown(); }    public boolean isTerminated() { return e.isTerminated(); }    public boolean awaitTermination(long timeout, TimeUnit unit)        throws InterruptedException {        return e.awaitTermination(timeout, unit);    }    public Future<?> submit(Runnable task) {        return e.submit(task);    }    public <T> Future<T> submit(Callable<T> task) {        return e.submit(task);    }    public <T> Future<T> submit(Runnable task, T result) {        return e.submit(task, result);    }    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)        throws InterruptedException {        return e.invokeAll(tasks);    }    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,                                         long timeout, TimeUnit unit)        throws InterruptedException {        return e.invokeAll(tasks, timeout, unit);    }    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)        throws InterruptedException, ExecutionException {        return e.invokeAny(tasks);    }    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,                           long timeout, TimeUnit unit)        throws InterruptedException, ExecutionException, TimeoutException {        return e.invokeAny(tasks, timeout, unit);    }}

也是Executors的内部类,这里有个设计概念,代理。
把被代理对象ExecutorService作为私有成员变量,然后仅暴露想暴露的方法。(学习了,自己以后开发过程中也可以这么用)

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

按需自动扩容线程池。核心线程数为0,keepAliveTime为60s,队列为同步队列。适合执行时间短,任务量大的场景。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {    return new ScheduledThreadPoolExecutor(corePoolSize);}/** * Creates a new {@code ScheduledThreadPoolExecutor} with the * given core pool size. * * @param corePoolSize the number of threads to keep in the pool, even *        if they are idle, unless {@code allowCoreThreadTimeOut} is set * @throws IllegalArgumentException if {@code corePoolSize < 0} */public ScheduledThreadPoolExecutor(int corePoolSize) {    super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,          new DelayedWorkQueue());}

可以创建一个延迟或周期执行任务的线程池。(具体实现还没细究)

总结一下,几种线程池对于使用方面还是比较容易区分,但是具体该配置多少线程数,这个需要实践才能得出结果(基础环境和业务环境千变万化)。一般可以根据任务的类型类配置(网上的得来的经验,参考一下,使用的时候还是得多多调试。):
CPU密集型型的任务,就要压榨CPU,不要频繁切换,参考值:cpu核心数+1
IO密集型的人物,参考值:2*cpu核心数
最后再强调一下,不要偷懒,一定要经过调试再选择正如的线程数量。

原创粉丝点击