为什么需要线程池?
在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题.因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有如下好处:
- 使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建和销毁时造成消耗
- 由于没有线程创建和销毁时的消耗,可以提高系统响应速度
- 通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行线程数量的大小等
下面是线程池的工作原理图
线程池的分类
一、ThreadPoolExecutor
子类 | 说明 |
---|---|
newCachedThreadPool | 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的ThreadFactory创建新线程.特征:(1)线程池中数量没有固定,可达到最大值(Interger.MAX_VALUE) . (2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟) (3)当线程池中没有可用线程,会重新创建一个新线程 |
newFixedThreadPool | 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在任意点,在大多数nThread线程会处于处理任务的活动状态,如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待,如果在关闭前的执行期间由于失败而导致线程终止,那么一个新线程将代替它执行后续的任务(如果需要).在某个线程被显式地关闭之前,池中的线程将一直存在. 特征:(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量 (2)线程可以重复被使用,在显式关闭之前,都将一直存在 (3)超出一定量的线程被提交时,需在队列中等待 |
newSingleThreadPool | 创建一个使用单个线程的Executor,以无界队列的方式来运行该线程.(注意:如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续任务).可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的,与其他有效的newFixedThreadPool不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程. 特征: 线程池中最多执行1个线程,之后提交的线程活动会排在队列中以此执行 |
1.newCachedThreadPool
public class CacheThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i =0;i<10;i++){
executorService.execute(new Task());
}
executorService.shutdown();
}
}
2.newFixedThreadPool
public class FixeThreadDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0;i<10;i++){
executorService.execute(new Task());
}
executorService.shutdown();
}
}
3.newSingleThreadPool
public class SingleThreadExecutor {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i =0;i<10;i++){
executorService.execute(new Task());
}
executorService.shutdown();
}
}
二、ScheduledThreadPoolExecutor
子类 | 说明 |
---|---|
newSingleThreadScheduledExecutor | 创建一个单线程执行程序,它可安排在给定程序延迟后运行命令或者定期地执行. 特征:(1)线程池中最多执行1个线程,之后提交的线程活动将会安排在队列中以此执行. (2)可定时或者延迟执行线程活动 |
newScheduledThreadPool | 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行. 特征:(1)线程池中具有给定数量的线程,即便是空线程也将保留. (2)可定时或者延迟执行线程活动 |
1.newScheduledThreadPool
类型1:
public class ScheduledThreadPoolDemo {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
System.out.println(System.currentTimeMillis());
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("延迟三秒执行。。。");
System.out.println(System.currentTimeMillis());
}
},3, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();
}
}
类型2:
public class ScheduledThreadPoolDemo2 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
System.out.println(System.currentTimeMillis());
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("延迟1秒执行。。。每三秒执行一次");
System.out.println(System.currentTimeMillis());
}
},1,3, TimeUnit.SECONDS);
// scheduledExecutorService.shutdown();
}
}
三、ForkJoinPool
子类 | 说明 |
---|---|
newWorkStealingPool | 创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传入并行级别参数,将默认为当前系统的CPU个数 |
线程池的生命周期
线程池的生命周期一共两个状态,RUNNING运行状态和TERMINATED终止状态,但在从running状态到terminated状态的时候,中间会出现SHUTDOWN、STOP、TIDYING三种不同的过度状态
1、在调用shut down()方法时,过度状态为SHUTDOWN
2、在调用shut down now()方法时,会进入STOP状态
3、最终两种状态都会进入到TIDYING状态进行回收.SHOUTDOWN状态会将线程池中的所有工作都进行完,才会进行回收,是非暴力的;STOP状态会将正在进行中的任务强制关闭,是暴力的(不推荐).
理论解释:
- RUNNING: 能接受新提交的任务,并且也能处理阻塞队列中的任务
- SHUTDOWN: 关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
- STOP: 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程
- TIDYING: 如果所有的任务都已终止了,workerCount(有效线程)为0,线程池进入该状态后会调用terminated()方法进入TERMINATED状态
- TERMINATED: 在terminated()方法执行完后进入该状态,默认terminated()方法中什么也没有做
注意:不要将线程池的生命周期和线程的生命周期搞混了,面试的时候可得听清楚了,问清楚了再回答😂
线程池的创建
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //存活时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //工厂模式
RejectedExecutionHandler handler //饱和策略){
拒绝策略
- ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectExecutionException异常(推荐)
- ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务,但是不抛出异常
- ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy: 由调用线程处理该任务
executor方法执行逻辑
- 如果当前运行的线程少于corePoolSize,则会创建新的线程来执行新的任务;
- 如果运行的线程个数等于或者大于corePoolSize,则会将提交的任务存放到阻塞队列workQueue中;
- 如果当前workQueue队列已满的话,则会创建新的线程来执行任务;
- 如果线程个数已经超过了maximumPoolSize,则会使用饱和策略RejectedExecutionHandler来进行处理
Executor和Submit
submit是基于方法Executor.execute(Runnable)的延伸,通过创建并返回一个Future类对象可用于取消执行和/或等待完成
public class Task implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return Thread.currentThread().getName()+"isRunning";
}
}
public class Test {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i=0;i<10;i++){
Future<?> submit = executorService.submit(new Task());
try {
String str = (String) submit.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
}
线程池的关闭
关闭线程池,可以通过shutdown和shutdownNow两个方法
原理:遍历线程池中的线程,然后依次中断
- shutdownNow 首先将线程池的状态设置为STOP,然后尝试停止所有正在执行和未执行任务的线程,并返回等待执行任务的列表
- shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程