基本介绍

Java多线程提供了一个”java.util.concurrent”包,该包里提供了与多线程有关系的类和接口。

类或接口

介绍

Executor

执行线程的工具接口

Executors

提供多个线程池的工具包

ExecutorService

线程池接口

ThreadPoolExecutor

真正线程池类,实现了ExecutorService

ScheduledExecutorService

能周期性和延时执行的线程池接口

ScheduledThreadPoolExecutor

能周期性和延时执行的线程池类

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 - 池中所保存的核心线程数,包括空闲线程。

maximumPoolSize-池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为在关闭前多余的空闲线程等待新任务的最长时间,也称为缓存时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的阻塞队列。此队列仅保持由 execute方法提交的 Runnable任务。

threadFactory - 执行程序创建新线程时使用的工厂。

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

ThreadPoolExecutor是Executors包里各种线程池的底层实现类。线程池分为:限长阻塞队列无界线程池(如:newCachedThreadPool),无界阻塞队列定长线程池(如:newFixedThreadPool),限长阻塞队列定长线程池。这主要靠maximumPoolSize、workQueue 、keepAliveTime 配合使用。定长不定长依赖maximumPoolSize是否为Integer.MAX_VALUE;缓存不缓存看keepAliveTime 是否为0;限长不限长看workQueue 所用的数据结构。

ThreadPoolExecutor基本工作流程如下:
1. 当有新任务来临,当前线程数< corePoolSize 时,首先新建线程直接执行新任务,而不会放入队列。
2. 当线程数>=corePoolSize 时,首先尝试将线程放入队列,而不新建线程。
3. 若队列长度没有限制,则判断是否存在空闲进程。若有,让空闲进程执行任务;否则在队列等待。
4. 若队列长度超过限制,判断当前线程数与maximumPoolSize大小关系。若当前线程数< maximumPoolSize,则新建一个线程执行任务;否则任务被拒绝,将由handler 处理。

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
源码如下:

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

newCachedThreadPool的corePoolSize被设置为0,即corePoolSize为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximum是无界的。这里keepAliveTime设置为60秒,意味着空闲的线程最多可以等待任务60秒,否则将被回收。

newCachedThreadPool使用没有容量的SynchronousQueue作为主线程池的工作队列,它是一个不存储元素阻塞队列,每次要进行offer操作时必须等待poll操作,否则不能继续添加元素。每个插入操作必须等待另一个线程的对应移除操作。这意味着,如果主线程提交任务的速度高于线程池中处理任务的速度时,newCachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU资源。

线程池中的线程是被线程池缓存了的,也就是说,线程没有任务要执行时,便处于空闲状态,处于空闲状态的线程并不会被立即销毁(会被缓存住),只有当空闲时间超出一段时间(默认为60s)后,线程池才会销毁该线程(相当于清除过时的缓存)。新任务到达后,线程池首先会让被缓存住的线程(空闲状态)去执行任务,如果没有可用线程(无空闲线程),便会创建新的线程。

执行过程:
1.首先执行SynchronousQueue.offer(Runnable task)。如果在当前的线程池中有空闲的线程正在执行SynchronousQueue.poll(),那么主线程执行的offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行。,execute()方法执行成功,否则执行步骤2

2.当线程池为空(初始maximumPool为空)或没有空闲线程时,配对失败,将没有线程执行SynchronousQueue.poll操作。这种情况下,线程池会创建一个新的线程执行任务。

3.在创建完新的线程以后,将会执行poll操作。当步骤2的线程执行完成后,将等待60秒,如果此时主线程提交了一个新任务,那么这个空闲线程将执行新任务,否则被回收。因此长时间不提交任务的CachedThreadPool不会占用系统资源。

newFixedThreadPool

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

maximumPoolSize和corePoolSize大小都由传入参数决定,都设计为nThreads。又因为LinkedBlockingQueue是无界队列,任务可以一直添加进去。所以newFixedThreadPool会一直保持有nThreads个线程在线程池中,而且是按顺序从LinkedBlockingQueue中取任务。
若提交任务的速度比完成任务的速度快时,LinkedBlockingQueue会一直添加任务,这样导致LinkedBlockingQueue一直增长,消耗大量内存。
工作流程:
1. 新任务被提交,若当前线程数< corePoolSize,直接新建线程处理任务。
2. 新任务被提交,若当前线程数 = corePoolSize时,任务被加入到LinkedBlockingQueue中。
3. 有线程处于空闲状态,从LinkedBlockingQueue中取出任务执行。

newSingleThreadExecutor

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

可以看出newSingleThreadExecutor的maximumPoolSize和corePoolSize都为1,表明只能有一个线程工作,而且任务可以不断加入到LinkedBlockingQueue中。

newScheduledThreadPool

ScheduledThreadPoolExecutor继承于ThreadPoolExecutor。

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

可以看到由编程者指定corePoolSize,而maximumPoolSize为Integer.MAX_VALUE,表示无限大。而阻塞队列使用DelayedWorkQueue,这是一个以堆为基础的数据结构,支持动态增加容量。所以它的工作流程类似与newFixedThreadPool,只不过阻塞队列不一样。