时间:2022-11-17 10:15:37 | 栏目:JAVA代码 | 点击:次
线程池是很常用的并发框架,几乎所有需要异步和并发处理任务的程序都可用到线程池。
使用线程池的好处如下:
线程池的原理非常简单,这里用处理流程来概括:
使用工作队列,是为了尽可能降低线程创建的开销。工作队列用阻塞队列来实现。
阻塞队列(BlockingQueue)是指支持阻塞的插入和移除元素的队列。
原理:使用通知者模式实现。当生产者往满的队列中添加元素时,会阻塞生产者。消费者移除元素时,会通知生产者当前队列可用。
阻塞队列有以下三种类型,分别是:
主要包括ArrayBlockingQueue(数组),LinkedBlockingQueue(链表)两种。有界队列大小与线程数量大小相互配合,队列容量大线程数量小时,可减少上下文切换降低cpu使用率,但是会降低吞吐量。
比较常用的是LinkedTransferQueue。FixedThreadPool就是用这个实现的。无界阻塞队列要慎重使用,因为在某些情况,可能会导致大量的任务堆积到队列中,导致内存飙升。
SynchronousQueue。不存储元素的阻塞队列,每一个put操作必须等待一个take操作,否则不能继续添加元素。用于实现CachedThreadPool线程池。
各个线程池所使用的任务队列映射关系如下:
线程池阻塞队列
FixedThreadPoolLinkedBlockingQueueSingleThreadExecutorLinkedBlockingQueueCachedThreadExecutorSynchronousQueueScheduledThreadPoolExecutorLinkedBlockingQueue
ThreadPoolExecutor是Java线程池的实现类,是Executor接口派生出来的最核心的类。依赖关系图如下:
这里不得不提到Executor框架,该框架包含三大部分,如下:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler)
构造方法如下:
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; }
参数说明:
使用工具类Executors可创建三种类型的线程池:FixedThreadPool、SingleThreadExecutor、CachedThreadPool。本质上也是调用上述构造方法。理解了前文的参数解释,下面三种线程池也就容易理解了。
FixedThreadPool
可重用固定线程数的线程池。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
工作流程如下:
SingleThreadExecutor
单个worker线程的线程池
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
SingleThreadExecutor与FixedThreadPool的区别在于,maximumPoolSize和corePoolSize都设置成了1,其它参数都一样。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
CachedThreadPool将corePoolSize设置为0,maximumPoolSize设置为无限大,同时使用了一个没有容量的工作队列SynchronousQueue。这个线程池没有固定的核心线程,而是根据需要创建新线程。
工作流程:
调用shutdown或者shutdownNow方法可关闭线程池。原理是遍历线程池中所有工作线程,调用interrupt方法来中断线程。
这里涉及到ThreadPoolExecutor中定义的线程的五种状态
// 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;
当队列和正在执行的任务都为空时,由SHUTDOWN转化为TIDYING;当正在执行的任务为空,由STOP转化为TIDYING。
本博客从线程池的原理介绍作为切入点,分析了线程池中尤为关键的组件:阻塞队列。同时分析了线程池的核心实现类ThreadPoolExecutor。以线程池的创建和关闭的思路,梳理了相关知识点,包括三种常用线程池介绍以及线程池五种状态。