一、我们为什么使用线程池?
我们在处理的任务较少时,可以手动通过继承Thread类,实现Runnable接口来进行创建线程去处理,但是在高并发的场景下,需要处理的任务量很多,频繁的创建线程会大大降低系统的效率。此时,我们就可以使用线程池,也是企业中应用最多的,线程池中的线程执行完一个任务后可以复用,并不被销毁。合理的使用线程池可以达到:
1. 减少资源的开销。通过复用线程,降低创建销毁线程造成的消耗。
2. 多个线程并发执行任务,提高系统的响应速度。
3. 可以统一的分配,调优和监控线程,提高线程的可控性。
二、创建线程池的五大参数
我们在使用线程池来创建线程:
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// Executors.newSingleThreadExecutor();
// Executors.newCachedThreadPool();
// Executors.newScheduledThreadPool(5);
try {
for (int j = 0; j < 10; j++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"正在运行");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
可以使用newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool这四种方式来创建线程池,下面追溯到源码来看看是怎么实现的?
//newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
前三种方式都是ThreadPoolExecutor类的构造方法去创建线程的,构造方法中有5个参数,每种方式的入参是不同的。通过参数值的不同来分析每种方式的特点。下面我们来看每个参数表示什么
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //线程存活时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue //任务队列,已经提交 但是未被执行的任务
) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
核心线程数:在创建了线程池后,有任务请求时,就会安排线程去执行任务,当线程池中的线程数超过corePoolSize后,将请求的任务放在任务队列中;
最大线程数:线程池中允许容纳同时执行的最大线程数,必须大于等于1;
线程存活时间:如果当前线程池中的线程数超过corePoolSize时,多余的线程空闲时间超过keepAliveTime时,就会被销毁,直到只剩下核心线程数为止;
任务队列:存放已经提交但是没有被执行的任务,常见的3种队列:
LinkedBlockingQueue:无界队列;SynchronousQueue:直接队列;ArrayBlockingQueue:有界队列
创建线程池的方式特点:
根据三种创建线程池方式的参数不同,我们来分析各自的特点:
newFixedThreadPool传入的任务队列是,LinkedBlockingQueue无界队列,可以存放无限制的任务请求,当请求越来越多并且无法及时处理完毕时,出现请求堆积,占用大量内存,可能会出现OOM;
newsingleThreadExecutor:和上边的方式基本相同,但是直接将核心线程数设置为1,但是也有同样的问题,可能出现占用大量内存的情况;
newCachedThreadPool:将最大线程数设置成最大值,可能会出现OOM问题。