Java 使用线程池
目录
一.介绍
二.ThreadPoolExecutor
2.1 认识ThreadPoolExecutor
2.2 ThreadPoolExecutor的构造方法列表
2.3 任务队列分类
2.4 线程工厂
2.5 拒绝策略
三.使用Executors快速创建线程池
3.1 Executors介绍
3.2 newFixedThreadPool-固定线程数量的线程池
3.3 newSingleThreadPool-只有一个线程的线程池
3.4 newCachedThreadPool-缓存线程池
一.介绍
本文主要介绍线程池的创建、线程任务队列分类以及线程池的扩展相关内容,只包含比较基础的知识,可以用来复习知识。
二.ThreadPoolExecutor
2.1 认识ThreadPoolExecutor
ThreadPoolExecutor,可以理解为ThreadPool+Executor(线程池+执行器),执行器会使用线程池中的线程来处理提交的任务,可以看下面的继承关系图:
2.2 ThreadPoolExecutor的构造方法列表
创建线程池,就是创建ThreadPoolExecutor实例对象,需要注意的是ThreadPoolExecutor有多个构造方法,如下所示:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:核心线程数(作用相当于一个门限值,看后面的介绍就知道了)
maximumPoolSize:线程池中线程的最大数量;
keepAliveTime,TimeUnit:当线程池中的线程数量超过corePoolSize后,多余的空闲线程存活的时间,也就是超过corePoolSize的空闲线程在多长时间后会被销毁;
workQueue:任务队列,用来保存已经提交但是并没有被执行的任务;
threadFactory:线程池中线程的创建工厂(默认使用Executors.DefaultThreadFactory);
handler:拒绝策略,当任务来不及被处理时被拒绝的处理操作;
2.3 任务队列分类
SynchronousQueue(同步队列):
ArrayBlockingQueue(有界队列)
LinkedBlockingQueue(无界队列)
PriorityBlockingQueue(优先队列)
DelayQueue
DelayQueue是一个阻塞队列,用在定时周期任务中作为任务队列。
2.4 线程工厂
熟悉设计模式的话,就知道XX工厂,就是用来创建XX的,所以ThreadFactory就是用来创建线程的,一般来说,我们直接使用默认的线程工厂即可,也就是Executors.DefaultThreadFactory;
但是有时候我们需要对线程的创建做一些扩展,就可以通过对创建自己的线程工厂来实现,可以参考这篇博客,利用线程自定义线程工厂来对线程池中的线程进行命名的方式:Java的每个Thread都希望拥有自己的名称
注意,可以通过继承ThreadPoolFactory来扩展线程池。
2.5 拒绝策略
当任务不能提交到任务队列时,执行的操作,称为拒绝策略,JDK提供了4种拒绝策略(实现了RejectedExecutionHandler接口)。
AbortPolicy:当不能向任务队列中提交任务时,就抛出异常,默认的拒绝策略;
DiscardOldestPolicy:丢弃最早进入等待队列(还未来得及处理)的任务,然后让新的任务加入队列;
DiscardPolicy:将新的任务直接丢弃;
CallerRunsPolicy:交给当前线程进行处理任务;
处理上面提供的拒绝策略,我们也可以根据自己的实际需求自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可,下面是一个简单示例:
package cn.ganlixin.thread; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; @Slf4j public class MyRejectedExecutionHandler implements RejectedExecutionHandler { /** * 拒绝策略处理逻辑 * * @param task 提交到任务队列被拒绝的任务 * @param executor 执行任务的执行器(线程池) */ @Override public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) { // 在这里定义逻辑,我这里只是输出一串文字 log.error("提交任务被拒绝"); } }
使用示例:
package cn.ganlixin.thread; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*; @Slf4j public class TestThread { public static void main(String[] args) throws InterruptedException { Runnable runnable = () -> { Thread thread = Thread.currentThread(); String name = thread.getName(); log.info("thread {} running", name); try { TimeUnit.SECONDS.sleep(5); // 休眠5秒 } catch (InterruptedException ignored) { } }; int corePoolSize = 2; int maximumPoolSize = 4; // 最多4个线程 int keepAliveSecond = 5; ExecutorService executorService = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveSecond, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), // 任务队列容量为2 Executors.defaultThreadFactory(), new MyRejectedExecutionHandler()); // 提交8个任务,因为线程池最多有4线程,再加上2个任务队列的任务,所以有2个任务会被拒绝 for (int i = 0; i < 8; i++) { executorService.submit(runnable); } // 输出如下: // thread pool-2-thread-3 running // thread pool-2-thread-2 running // thread pool-2-thread-4 running // thread pool-2-thread-1 running // 提交任务被拒绝 // 提交任务被拒绝 // thread pool-2-thread-4 running // thread pool-2-thread-2 running Thread.currentThread().join(); } }
三.使用Executors快速创建线程池
3.1 Executors介绍
我们可以根据自己的实际需要来实例化ThreadPoolExecutor创建线程池,除此之外,Java中的Executors还提供了多个静态方法来创建各种类型的线程池(可以拿来即用)。
可以利用Executors类的各个静态方法来创建线程池,创建的线程池特点从名称上就可以看出来,需要注意的这些静态方法内部其实也是利用ThreadPoolExecutor类来创建实例对象,只不过根据不同的类型设置了不同的参数。
3.2 newFixedThreadPool-固定线程数量的线程池
固定线程数量的线程池,其实是在实例化ThreadPoolExecutor时,将corePoolSize和MaxPoolSize都设置为同一个值;
需要这种方式创建的线程池使用的LinkedBlockingQueue作为任务提交队列,并且没有设定队列的容量,也就是说,如果任务没有来不及执行,就会将其提交到等待队列中(如果无限制的提交,将导致系统资源枯竭)。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
3.3 newSingleThreadPool-只有一个线程的线程池
创建只有1个线程的线程池,内部使用FinalizableDelegatedExecutorService进行了代理,当线程池中的线程被关闭后,会重新创建一个线程来继续工作。
注意仍旧使用的未限制容量的LinkedBlockingQueue。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
3.4 newCachedThreadPool
创建缓存线程池,corePoolSize为0,maxPoolSize为Integer.MAX_VALUE(可以理解为不限制);
线程在空闲60秒后就会被销毁;
使用SynchronousQueue,也就是说,只要有新任务提交,如果有空闲线程,则让空闲线程处理任务;如果没有空闲线程,则会创建新线程来处理。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }