一、为什么要用线程池
1、使用线程池之前
- 一开始接触多线程的时候,我们知道有三种方式可以创建多个线程,一种方式是继承Thread类本身;另外一种方式是实现Runnable接口;第三种方式是实现Callable接口并利用Future;
- 线程无返回值的时候,一般通过实现Runnable接口来创建新的线程,而不是采用继承Thread类来实现多线程,因为java是单继承的,实现接口更加灵活;实现该接口需要重写run方法,然后把该接口的实例传递给Thread类的实例,通过Thread类的start方法来启动;
- 线程有返回值的时候,一般通过实现Callable接口并利用Thread类来启动线程并通过Future接口实例接收返回值;
- 关于Thread和Runnable接口,我是如下理解的:javaSE先提供了Thead类,通过继承Thread类来实现多线程,由于java单继承为了方便,又提供了Runnable接口,但此时已经有了Thread类,因此简化Runnable接口,只需要重写run方法即可,而真正线程运行起来还需要其它的方法比如start等,则重复使用Thread类中的方法,因此Runnable接口的实例需要传入一个Thread类实例中去管理(启动等等操作)
- 而Callable接口就是Runnable接口的补充,从而实现线程执行后返回结果值;
- 上述方式实现多线程存在的问题有:线程的管理完全由自己完成,无法对线程进行细致地控制和管理,比如说如果想控制线程的数量,若是自己在代码中启动过多的线程,或者是代码中没有及时关闭线程,代码在服务器上跑的时候造成线程数量累积,浪费资源;而线程的启动和关闭是非常消耗资源的,如果频繁的启动关闭线程,会造成资源的浪费,甚至是有效代码的执行时间比线程的启动关闭等额外操作执行时间还要短很多;
- 其实除了以上三种方式,还有第三种方式用于创建线程,这就是线程池的方式。
2、使用线程池之后
- 线程池使用很简单,java.util.concurrent下提供了线程池的包
- 这个包下提供了多重线程池,可以灵活的创建所需的线程池
- 通过使用线程池可以减少资源的消耗、提高代码的响应速度、增加线程的可控性
- 可以简单地将线程池理解为存放线程的池子,我们可以通过java.util.concurrent下的工具,根据自己的需求选择合适的池子,合适大小的池子,以及其它池子的设定,并在需要一个线程的时候在池子中取,或者把线程池看作是一个可以定制的容器,容器中存放线程。
3、总结
- 原始的三种创建线程的方式,继承接口的两种实际上也是通过 new Thread()来实现线程的创建和启动等的管理,只不过线程需要执行的任务写在实现了Runnable或Callable接口的类中,线程池的方式便是替代new Thread()的方式,通过线程池管理线程,并维护一个任务队列,任务提交到线程池后进入任务队列,由线程池管理线程的调用和任务的执行,而任务仍是实现了Runnable或Callable接口的类
- 线程池的使用简单灵活,能用线程池就不用原始的三种创建线程的方式
二、线程池怎么用
1、创建线程池
- <mark>newFixedThreadPool</mark>:一个固定线程数量的线程池,用于已知并发压力的情况下,对线程数做限制。
- <mark>newSingleThreadExecutor</mark>:一个单线程的线程池,用于需要保证顺序执行的场景,并且只有一个线程在执行。
- newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
- newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
- newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
- 上述解释是看的别人的介绍,自己目前感觉用处比较大的就是第一个和第二个,尤其是地一个,因为目前我做的springboot的项目都是并发压力已知的。
- 通过工具类Executors的静态方法来获取线程池
// 固定线程池中最大可用线程数量为10,当新的任务需要线程池中的线程执行时,如果池子中有空闲线程,则利用空闲线程执行此任务;
// 如果线程池中此时无空闲线程,则该任务进入队列等待,直到有空闲线程时再执行;
ExecutorService fixedThreadPoolService = Executors.newFixedThreadPool(10);
ExecutorService singleThreadExecutorService = Executors.newSingleThreadExecutor();ExecutorService cachedThreadPoolService = Executors.newCachedThreadPool();ExecutorService scheduledThreadPoolService = Executors.newScheduledThreadPool(10);ExecutorService workStealingPoolService = Executors.newWorkStealingPool();
```#### 2、当然会有需要使用线程执行的任务
- 创建任务的方式有两种,实现Runnable接口或是实现Callable接口
- 实现Runnable接口需要重写接口中的run方法,该方法无返回值
- 实现Callable接口需要重写接口中的call方法,该方法有返回值
- 要实现有返回值的任务,除了实现Callable接口,还要使用Future接口,该接口的实例可以作为线程执行结果的返回值的接收者,通过该接口实例接收返回值或返回的异常```
import java.util.concurrent.Callable;public class MyCallable implements Callable {
private String str;
private boolean flag; public MyCallable(String str, boolean flag){
this.str = str;
this.flag = flag;
} @Override
public String call() throws Exception {
if (flag)
return this.str;
else{
while (true){}
}
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class CallableAndFutureLearning {
public static void main(String[] args) {
MyCallable myCallable1 = new MyCallable("myCallable1", true);
MyCallable myCallable2 = new MyCallable("myCallable2", false);
MyCallable myCallable3 = new MyCallable("myCallable3", true); ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
Future future1 = fixedThreadPool.submit(myCallable1);
Future future2 = fixedThreadPool.submit(myCallable2);
Future future3 = fixedThreadPool.submit(myCallable3); try {
System.out.println("future1:" + future1.get());
System.out.println("future2:" + future2.cancel( true));
System.out.println("future3:" + future3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} fixedThreadPool.shutdownNow();
}
}
3、通过线程池中的线程执行任务
- 如上所示
- 两种方法提交任务到线程池 executor()和submit()
- executor()方法提交的任务无返回值
- submit()方法提交的任务有返回值,返回值通过Future类的实例接收,返回值可能时正常的输出也可能是异常
4、关闭线程池
- 如上所示
- 两种方式关闭线程池,shutdown 和 shutdownNow 两个方法关闭线程成
- shutdown 不是立即关闭线程池,而是不在接收新的任务,线程池中正在执行的任务会继续执行,所有线程都执行结束后关闭线程池
- shutdownNow 不再接收新的任务,线程池中的线程没有执行任务,则直接关闭线程池;有正在执行的任务,则尝试停止任务,并将任务队列中等待的任务清除,然后关闭线程池
三、线程池的其它知识
- 查看java.util.concurrent包下的类和接口及其方法,很清晰地看到线程池的各种使用