Java线程池

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 bits
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;

代码中会有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
开始执行 14
task 0执行完毕
task 4执行完毕
开始执行 5
task 3执行完毕
task 1执行完毕
开始执行 8
task 2执行完毕
开始执行 9
task 12执行完毕
开始执行 7
task 11执行完毕
task 10执行完毕
开始执行 6
task 14执行完毕
task 13执行完毕
线程池数:10,队列数:1
开始执行 15
开始执行 16
线程池数:10,队列数:1
线程池数:10,队列数:1
开始执行 17
开始执行 18
线程池数:10,队列数:1
线程池数:10,队列数:1
开始执行 19
task 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核心数
最后再强调一下,不要偷懒,一定要经过调试再选择正如的线程数量。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值