线程池的作用:
1、使用线程池可以有效地管理线程,控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能下降,甚至导致JVM崩溃。
2、使用线程池可以减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者线程过度切换的问题。
线程池的创建:
从Java 5开始,Java内建支持线程池,可通过Executors工厂类来产生线程池。但是,一般不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式去创建,这样可以明确线程池的运行规则,避免资源耗尽的风险。Executors创建线程池的弊端如下:
1、FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2、CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
1、通过ThreadPoolExecutor创建线程池:
ThreadPoolExecutor核心构造器如下,其余3中构造器都是通过调用核心构造器实现的。
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数解析:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:线程池维护线程所允许的空闲时间
当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime,才销毁。 - unit:空闲时间单位
- workQueue:请求队列
1、LinkedBlockingQueue:链表式阻塞队列,可显示指定队列大小,默认为队列大小为Integer.MAX_VALUE,先进先出。
2、ArrayBlockingQueue:数组式阻塞队列,必须显示指定队列大小,先进先出。
3、SynchronousQueue:同步队列,工作队列不保存任何任务,而是直接将任务提交给线程执行。
4、LinkedBlockingDeque:链表式双端阻塞队列,可显示指定队列大小,默认为队列大小为Integer.MAX_VALUE。
5、PriorityBlockingQueue:优先级阻塞队列,默认队列大小为11。存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue 不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。
6、LinkedTransferQueue:除了具有一般队列的操作特性外(先进先出),还具有一个阻塞特性,LinkedTransferQueue可以由 一对生产者/消费者线程进行操作,当消费者将一个新的元素插入队列后,消费者线程将会一直等待,直到某一个消费者线程将这个元素取走。 - threadFactory:加工提交到线程池的线程
一般使用默认线程工厂defaultThreadFactory(),其作用如下:
1、定义线程名称;
2、设置是否为后台线程;
3、设置线程优先级。 - handler:拒绝策略(四种)
1、AbortPolicy:默认拒绝策略。如果任务添加失败,会抛出RejectedExecutionException异常,程序直接退出。因而向线程池提交线程时,需要try…catch…进行异常处理。
2、CallerRunsPolicy:如果任务添加失败,由调用线程(一般为main线程)来执行被拒绝的任务。
3、DiscardPolicy:如果任务添加失败,直接丢弃待添加的任务,不进行处理。
4、DiscardOldestPolicy:如果任务添加失败,会将workQueue队列中最早添加的任务移除,再尝试添加。
线程池运作原理:
1、当线程池中的线程数小于corePoolSize,新提交的任务将创建一个新线程来处理该任务,即使线程池中存在空闲线程;
2、当线程池达到corePoolSize时,新提交任务将放到workQueue中,等待线程池任务调度执行;
3、当workQueue已满,且maximumPoolSize > corePoolSize,新提交的任务会创建新线程执行任务;
4、当提交任务数超过maximumPoolSize时,新提交任务RejectedExecutionHandle处理;
5、当线程池中线程数超过corePoolSize,空闲时间达到keepAliveTime,关闭空闲线程;
6、当设置allowCoreThreadTimeOut(true),线程池中core线程空闲时间达到keepAliveTime也将关闭。
2、通过Executors创建线程池:
- Executors.newFixedThreadPool(nThreads:5):
底层通过调用new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())实现。
(1)允许请求队列长度为Integer.MAX_VALUE,拒绝策略为AbortPolicy。
(2)LinkedBlockingQueue:链表式阻塞队列,默认队列大小为Integer.MAX_VALUE,先进先出。 - Executors.newSingleThreadExecutor():
底层通过调用new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())实现。
(1)允许请求队列长度为Integer.MAX_VALUE,拒绝策略为AbortPolicy。
(2)LinkedBlockingQueue:链表式阻塞队列,默认队列大小为Integer.MAX_VALUE,先进先出。 - Executors.newCachedThreadPool():
底层通过调用new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue())实现。
(1)允许创建线程数量为Integer.MAX_VALUE,拒绝策略为AbortPolicy。
(2)SynchronousQueue:同步队列,工作队列不保存任何任务,而是直接将任务提交给线程池执行。 - Executors.newScheduledThreadPool(corePoolSize:5):
底层通过调用new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())实现。
(1)允许创建线程数量为Integer.MAX_VALUE,拒绝策略为AbortPolicy。
(2)DelayedWorkQueue:延时工作队列,任务队列会根据任务延时时间的不同进行排序,延时时间越短地就排在队列的前面,优先被获取执行。
线程池的关闭:
主线程 (main线程) 结束,不管线程池中的线程任务有没有结束,程序都会结束。因此,主线程需要判断线程池中线程是否都处理完毕。两种处理方式如下:
方式一:
//尝试关闭线程池(线程池不在接收新的线程任务,并不会真的关闭线程池)
//等待已经开始的任务和请求队列中尚未开始的任务都执行完毕后,才关闭线程池。
threadPoolExecutor.shutdown();
//主线程循环判断线程池任务是否处理完毕
while (true) {
//当调用shutdown()方法后,并且所有提交的任务完成后返回为true
if (threadPoolExecutor.isTerminated()) {
//线程池任务都处理完毕,结束循环
break;
}
try {
//等待1s
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
方式二:
//尝试关闭线程池(线程池不在接收新的线程任务,并不会真的关闭线程池)
//等待已经开始的任务和请求队列中尚未开始的任务都执行完毕后,才关闭线程池。
threadPoolExecutor.shutdown();
try {
//所有线程任务在规定的时间结束,返回true,否则返回false
if (!threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
//取消已经开始的任务(中断这些任务),立马关闭线程池,仅仅返回任务队列中未执行的线程,不返回已经开始的线程
List<Runnable> list = threadPoolExecutor.shutdownNow();
System.out.println("未执行的线程:" + list);
}
} catch (InterruptedException e) {
List<Runnable> list = threadPoolExecutor.shutdownNow();
System.out.println("未执行的线程:" + list);
e.printStackTrace();
}
线程池的使用:
测试环境:
- 开发工具:IntelliJ IDEA
- Java版本:1.8
- Spring Boot版本:1.5.7.RELEASE
定义线程池
@Service
public class ThreadPoolTask {
public void threadPoolTask() {
//如果设置maximumPoolSize小于corePoolSize,则会报IllegalArgumentException异常
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 6,
0L, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(4));
//增加线程池拒绝策略
threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//核心线程设置空闲过期时间
threadPoolExecutor.allowCoreThreadTimeOut(true);
//定义线程任务
Runnable target = () -> {
System.out.println(Thread.currentThread().getName() + "开始任务..." );
//根据单位设置休眠时间,内部仍然调用Thread.sleep()方法
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束任务:" + Thread.currentThread().getName());
};
System.out.println("*****程序开始*****");
//提交10个线程任务
for(int i = 0; i < 10; i++) {
/* 1、execute提交的是Runnable类型的任务,而submit提交的是Callable或者Runnable类型的任务。
2、execute的提交没有返回值,而submit的提交会返回一个Future类型的对象。
3、execute提交的时候,如果有异常,就会直接抛出异常;而submit在遇到异常的时候,通常不会立马抛出异常,
而是会将异常暂时存储起来,等待你调用Future.get()方法的时候,才会抛出异常。*/
threadPoolExecutor.submit(target); //内部调用execute()方法
//threadPoolExecutor.execute(target);
}
//尝试关闭线程池(线程池不在接收新的线程任务,并不会真的关闭线程池)
//等待那些已经开始的任务执行完毕,等待任务队列中尚未开始的任务执行完毕。
threadPoolExecutor.shutdown();
/*
* 主线程(main)结束,不管线程池中的线程任务有没有结束,程序都会结束。
* 因此,主线程需要判断线程池中线程是否都处理完毕。
*/
while (true) {
//当调用shutdown()方法后,并且所有提交的任务完成后返回为true
if (threadPoolExecutor.isTerminated()) {
System.out.println("--> 所有子线程都结束了 <--");
break;
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("*****程序结束*****" );
}
}
测试线程池
@RunWith(SpringRunner.class)
@SpringBootTest
public class ScheduleTaskApplicationTests {
@Autowired
private ThreadPoolTask task;
@Test
public void contextLoads() {
task.threadPoolTask();
}
}
测试结果如下:
