为什么要使用线程池?
1.频繁的创建、销毁线程伴随着系统开销,会在很大程度上影响系统性能,效率会降低;
2.线程并发数量过大的情况下,线程直接抢占资源会导致系统阻塞;
3.线程池会对线程进行一些简单的管理。
实现
在java中管理线程的是java.util.concurrent包下的Executor接口。这个接口的具体实现类是ThreadPoolExecutor类。学习线程的话,主要是对这个类的构造函数进行一些配置。
ThreadPoolExecutor的构造函数
构造函数参数的不同又分为四个构造函数(有5个参数的,6个参数的,7个参数的),后面会具体讲解,请往下看。
构造函数中各个参数的意义
1.
int corePoolSize 该线程池中核心线程的数的最大值,线程池在新建线程的时候,如果当前的线程总数小于corePoolSize,则新建的是核心线程。
2.
int maximumPoolSize 该线程池中线程总数的最大值,线程总数=核心线程数+非核心线城数。
3.
long keepAliveTime 该线程池中非核心线城闭置状态下起始时长,一个非核心线城,如果闭置状态下的时长超过这个参数设定的时长,就会被销毁。
4.
TimeUnit unit 就是一个枚举类型
5.
BlockingQueue<Runnable> workQueue 2该线程池中的任务队列,维护着等待的对象,当所有的核心线城都在干活时,新添加的任务会到这个队列中等待被处理,如果队列满了,就会创建非核心线程执行任务。
6.
ThreadFactory threadFactory 创建线程的方式,这是一个接口
7.
RejectedExecutionHandler handler 抛异常专用,如果有错误产生,则由handler抛出
常用的4种线程池(根据业务场景选择)
1. CachedThreadPool()
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
根据源码可以看出这种线程池是没有核心线程的,在创建任务时,若有空闲的线程则复用,否则就创建一个新的线程,没有工作的线程,超过60s就会被销毁。
2.FixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
通过源码可以看出该线程池的最大线程数等于核心线程数,所以该线程池的的线程不会因为闭置状态起时被销毁。如果当前线程数小于核心线程,不会去复用之前的线程,会创建新的线程去执行任务;如果当前执行任务大于该线程的核心线程数,大于的任务会在队列中等待。
3.SingleThreadPool()
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
仅有一个工作线程执行任务,所有的任务遵循队列的入队出队规则
4.SchdeledThreadPool()
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory, handler); }
设置了最大线程数,核心线程数。这个线程是唯一一个有延迟执行和周期执行任务的线程池。