Java基础知识总结:深入浅出Java线程池

ThreadPoolExecutor

Java 中线程池的核心类为 ThreadPoolExecutor,它有五个具体的参数:

如果以下的文字解释你看的实在头疼,推荐你看看这种 图解线程池的文章

  1. corePoolSize:核心线程数,最初为 0,接收一个任务创建一个直到数目增长至 corePoolSize 为止,类似于 lazyinit 的形式,之后核心线程也不会被回收,而是一直以这个数目稳定存在,处理任务。

  2. maxPoolSize:当线程数目达到 corePoolSize 之后,过剩的任务便进入 workQueue 等待,核心线程处理完手里的任务后就会从队列里取出一个任务,但是如果 workQueue 满了呢?核心线程处理不来,等待队列也满员了,这时咋办?Java 的处理办法是新创建一些临时线程来紧急处理过多的任务。maxPoolSize 指代的是核心线程数目加上临时线程数目的上限。

  3. keepAliveTime:临时线程毕竟不是核心线程,任务多的时候需要临时工处理下,任务少了临时工就可以辞退了防止资源浪费,因此 keepAliveTime 定义的就是临时线程的生存时间,临时线程空闲超过此事件会被回收。

  4. TimeUnit:定义的是 keepAliveTime 的单位,有若干常量可以选择。

  5. workQueue:定义的是过剩的任务存放的队列,可以定义长度,也可以不定义长度,但是不定义长度的任务队列可能产生内存暴涨导致 OOM 的风险

  6. threadFactory:线程创建工厂,有默认实现因此是个可选参数,一般来说在项目中最好自定义线程名称,推荐自定义不要使用默认的。

  7. handler:当 maxPoolSize 也无法处理过来时,那么这部分任务只能被遗憾地处理掉,处理方式可以是抛异常、可以是丢弃、可以是自定义的处理方式,handler 就是定义具体处理方法的参数。

Executors

另外还提供了一个工厂类 Executors,用户可以使用它里面的静态方法来创建固定类型的线程池,注意其底层实际上也是借助 ThreadPoolExecutor 来实现的。《阿里Java编程规范》中提到,不推荐使用这种方式创建线程池,一方面是线程池参数无法直观体现,二来这种创建方式存在无限任务队列导致 OOMFullGC 的风险。

具体 Executors 提供以下几种类型的线程池封装,从而简化线程池创建步骤,下面未定义 handler 的均使用的是默认实现,功能是直接拒绝抛出异常:

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

newSingleThreadExecutor

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

从源码中可以看到传入的 corePoolSizemaxPoolSize 都是 1,意味着临时线程数目限制为 0,使用了无界限的阻塞队列 LinkedBlockingQueue。它优点是保证了所有任务的都是被顺序执行,并且不允许外部改动线程池实例,因此可以避免使用的线程数目发生改变。

newFixedThreadPool(int nThreads)

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

源码中同 newSingleThreadExecutor 一致,只不过传入的 corePoolSizemaxPoolSize 都是 n,限制了核心线程数目是 n,如果队列满了只能等待空闲的核心线程,不会创建临时线程,也存在使用无界任务队列的问题。

newCachedThreadPool()

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

源码可以看到,它不创建核心线程,只创建临时工作线程,闲置超出 60s 即回收,因此 CachedThreadPool 适合处理短时间任务,只需要雇佣临时工的那种,好处是短时间任务处理完就可以辞退,不会浪费资源,造成核心线程长时间空等的情况。在实际编码中这是个很好的思路。

newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize)

创建的是 ScheduledExecutorService,可以进行定时或周期性的工作调度, 区别在于单一工作线程还是多个工作线程。

newWorkStealingPool(int parallelism)

这是一个 Java 8 新加入这个创建方法,其内部会构建 ForkJoinPool,利用 Work-Stealing 算法,并行地处理任务,不保证处理顺序。

Runnable 和 Callback

创建线程的三种方式,一是继承 Thread,二是实现 Runnable 接口,三是实现 Callback 接口,其中第一种受限于 Java 继承机制且过于重量级不推荐使用;后两者接口的实现方式更轻量级,且便于复用,Callback 的优势在于可以有返回值,对于需要取得线程运行结果的场景需要选择 Callback

Future 和 FutureTask

Future 是一个接口,FutureTask 是其具体实现,用途是异步获取提交进线程池的线程的执行结果,需要配合 Callback 使用。

你可能感兴趣的:(Java,Tech,Java基础知识总结)