java线程池
1.线程池的核心概念
线程池ThreadPoolExecutor的构造函数有多个参数,但是最重要的参数是workQueue阻塞队列这个参数。线程池正是使用了阻塞队列的特性,使得线程能够一直留在池中,而不会因为run方法执行完成而销毁。线程池的线程会从阻塞队列中取出任务执行,当阻塞队列为空时,阻塞队列会阻塞当前线程(挂起),直到有新的任务进入队列才会被唤醒。所以当目前没有任务时,线程池的线程会被挂起(不消耗cpu),线程就可以一直存在于进程中(只不过被挂起了而已)或者说存在于线程池中。这种方式体现了‘池’的概念,这些线程可以重复使用,有任务时被唤醒执行,没有任务则挂起,减少线程创建和销毁的性能消耗。
- corePoolSize:核心线程数,不要被这个参数的名称迷惑,其实这个参数实际上是线程池线程数。当线程池实际线程数量小于corePoolSize时,线程池将会创建新线程并加入到线程池中;大于corePoolSize时,会将任务加入队列,而不会继续创建线程,只有队列已经满的情况下,并且当前线程数小于maxPoolSize,才会创建新线程来执行任务。但是这部分线程会在keepAliveTime之后被销毁。
- maxPoolSize:最大线程数
- keepAliveTime:线程存活时间(在corePore<当前线程数<maxPoolSize情况下有用)
- timeUnit:存活时间的时间单位
- workQueue:阻塞队列(用来保存等待被执行的任务,关于workQueue参数的取值,JDK提供了4种阻塞队列类型供选择:)
- ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
- InkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务
- SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于ArrayBlockingQuene;
- PriorityBlockingQuene:具有优先级的无界阻塞队列;
- threadFactory:线程工厂,主要用来创建线程;
- handler:表示当拒绝处理任务时的策略,有以下四种取值
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 5.当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
2.例子
public class ThreadPool { public static void main(String[] args) { ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5)); for (int i=0;i<15;i++){ poolExecutor.execute(new Task(i)); System.out.println("线程池中线程数目:"+poolExecutor.getPoolSize()+",队列中等待执行的任务数目:"+ poolExecutor.getQueue().size()+",已执行完成的任务数目:"+poolExecutor.getCompletedTaskCount()); } for(;;) { try { //if (taskNum < 10) { Thread.currentThread().sleep(3000); //} } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程池中线程数目:" + poolExecutor.getPoolSize() + ",队列中等待执行的任务数目:" + poolExecutor.getQueue().size() + ",已执行完成的任务数目:" + poolExecutor.getCompletedTaskCount()); } //poolExecutor.shutdown(); } } class Task implements Runnable { private int taskNum; public Task(int taskNum){ this.taskNum = taskNum; } @Override public void run() { System.out.println("正在执行task "+taskNum); try { //if (taskNum < 10) { Thread.currentThread().sleep(4000); //} } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task "+taskNum+"执行完毕"); } }
输出:
正在执行task 0
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完成的任务数目:0
正在执行task 1
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完成的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行完成的任务数目:0
正在执行task 2
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行完成的任务数目:0
正在执行task 3
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:0
正在执行task 4
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行完成的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行完成的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行完成的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行完成的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行完成的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行完成的任务数目:0
正在执行task 10
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行完成的任务数目:0
正在执行task 11
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行完成的任务数目:0
正在执行task 12
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行完成的任务数目:0
正在执行task 13
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完成的任务数目:0
正在执行task 14
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完成的任务数目:0
task 1执行完毕
task 3执行完毕
task 11执行完毕
task 10执行完毕
task 4执行完毕
task 2执行完毕
task 0执行完毕
task 14执行完毕
task 12执行完毕
task 13执行完毕
正在执行task 9
正在执行task 8
正在执行task 7
正在执行task 6
正在执行task 5
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:10
task 9执行完毕
task 6执行完毕
task 7执行完毕
task 8执行完毕
task 5执行完毕
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:15
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完成的任务数目:15
可以看到线程池线程的数量会维持coreSize的数量,大于core和小于max的线程会被回收。
3.Executors工厂类
Executors是ThreadPoolExecutor线程池的工厂类,根据不同的入参创建了四种不同类型的线程池,下面一一列举这四种线程池的特性。
1.FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
它是一种固定线程数量的线程池;
在创建LinkedBlockingQueue阻塞队列时,没有指定容量。那么这就意味着该线程池永远都不会拒绝任务;
所以keepAliveTime和handler参数就是无效的。
2.SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
顾名思义,它是一种只有单个线程的线程池;
它与FixedThreadPool的区别只是在于coreThread的数量。
3.CachedThreadPool
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
它是一个可以无限扩大的线程池;
corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
采用SynchronousQueue阻塞队列,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程;
因为SynchronousQueue阻塞队列的特性,它比较适合处理执行时间比较小的任务。
4.ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
它用来处理延时任务或定时任务。
CachedThreadPool比较难以理解因此这里提供一个例子
package threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*
* @author : LQ
* @since : 2019/1/18 14:50 Description:
*/
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(0,1000,60, TimeUnit.SECONDS,new SynchronousQueue());
for (int i=0;i<100;i++){
poolExecutor.execute(new Task(i));
}
for(;;) {
System.out.println("线程池中线程数目:" + poolExecutor.getPoolSize() + ",队列中等待执行的任务数目:" +
poolExecutor.getQueue().size() + ",已执行完成的任务数目:" + poolExecutor.getCompletedTaskCount());
}
//poolExecutor.shutdown();
}
}
class Task implements Runnable {
private int taskNum;
public Task(int taskNum){
this.taskNum = taskNum;
}
@Override
public void run() {
System.out.println("thread name:"+Thread.currentThread().getName());
}
}
输出:
线程池中线程数目:14,队列中等待执行的任务数目:0,已执行完成的任务数目:100
可以看到虽然提交了100个任务,但其实创建的线程只有7个(每次创建的线程数量都不一样)。如果任务的处理时间很长,那么就很可能会创建100个线程了。