Java线程池详解(一)

1、背景

说到线程池相信大家都不陌生吧,这里我们就不详细的解释了,我们直接来说说使用线程池的好处吧,可以概括为以下三部分。

  1. 重用线程池中的线程,避免因为线程的重复创建和销毁而带来的系统开销;
  2. 能有效的控制线程的最大并发数,防止线程过多而导致系统阻塞;
  3. 能够对线程进行简单的管理,并提供定时执行和循环执行的功能;

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是ExecutorService。

我们接下来看一下线程池中比较重要的几个类。

2、线程类详解

首先我们来看一张Executor类的关系图。

Java线程池详解(一)_第1张图片

线程池的主要类都在这张图中了,我们对其比较重要的几个类进行简单说明一下。

ExecutorService:真正的线程池接口;
ScheduledExecutorService:和Timer/TimerTask类似,解决那些需要任务重复执行的问题;
ThreadPoolExecutor:ExecutorService的默认实现;
ScheduledThreadPoolExecutor:继承ThreadPoolExecutor和实现ScheduledExecutorService接口,周期性任务调度的类实现;

要想配置一个最优的线程池还是比较复杂的,我们主要讲解ExecutorService的默认实现ThreadPoolExecutor类,ThreadPoolExecutor类有多个重载的构造器如下:

Java线程池详解(一)_第2张图片

无论哪个构造器其最终都会调用到以下这个构造器:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //任何一个配置数据小于0都会抛出异常
        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;
    }

在这里我们主要关注ThreadPoolExecutor构造器的参数含义:

(1)corePoolSize:池中的核心线程数,默认情况下核心线程数会一直存活,即使他们闲置,如果将allowCoreThreadTimeOut()设置为true,那么闲置的核心线程会有闲置超时策略,这个时间由keepAliveTime决定,超时后核心线程会被终止。

(2)maximumPoolSize:池中允许的最大线程数,当活动线程超过这个数字后,后续新任务会被阻塞。

(3)keepAliveTime:非核心线程闲置超时时长,超时后非核心线程会被回收,当allowCoreThreadTimeOut()被设置为true后,其同样作用于核心线程。

(4)unit:keepAliveTime 参数的时间单位,常用的是枚举类TimeUnit。

(5)workQueue:线程池的任务队列,此队列仅保持由 execute方法提交的 Runnable任务。

(6)threadFactory:线程工厂,为线程池提供新线程的功能,其只有一个方法newThread(Runnable r) 。

(7)handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的异常处理程序。

在这里系统为我们默认提供了threadFactory和handler两个参数,我们看一下系统默认提供的参数。

//系统默认提供的产生新线程的工厂类
static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        //重写其newThread方法
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
//系统默认提供的异常处理类
private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

系统提供的默认工厂类我们在这里就不阐述了,看其源码也了解其大致功能,在这里我们简单说一下RejectedExecutionHandler异常处理类,这是一个接口类其有唯一的方法rejectedExecution(Runnable r, ThreadPoolExecutor executor),当线程池无法执行新任务时,这个时候ThreadPoolExecutor会调用handler的rejectedExecution()方法来通知调用者,默认情况下rejectedExecution方法是直接抛出RejectedExecutionException异常。

ThreadPoolExecutor为RejectedExecutionHandler提供几个可选值,我们看以下图。

Java线程池详解(一)_第3张图片

由于handler我们不经常使用,所以这里就不详细介绍了。

3、配置线程池示例

接下来我们看一个配置ThreadPoolExecutor线程池的示例。

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;

    // 创建默认工厂
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "Thread " + mCount.getAndIncrement());
        }
    };

    // 创建任务队列
    private static final BlockingQueue sWorkQueue = new LinkedBlockingQueue<>(128);

    // 创建一个原生的线程池
    public static final ExecutorService THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE,
            KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

    //main方法测试
    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {
            final int index = i;
            THREAD_POOL_EXECUTOR.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

4、线程池总结

ThreadPoolExecutor在执行任务时大致有如下规则:

  1. 如果线程池中的线程数未达到核心线程数时,那么会直接启动一个核心线程来执行任务。
  2. 如果线程池中的线程数已经达到或者超过核心线程数,那么任务会被插入到任务队列中等待执行。
  3. 如果步骤2中无法将任务插入到任务队列中,那此时往往是任务队列已满,如果此时线程数未达到线程池规定的线程最大数,将立即启动一个非核心线程来执行任务。
  4. 如果步骤3中线程数已经达到线程池规定最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution来通知调用者。

你可能感兴趣的:(Java)