Java8——多线程(二):线程池

java中的 Future详解(以及ExecutorService中的各种方法)

线程池详解(包括Future和FutureTask)

在Java中,我们使用线程来实现程序的并发执行,线程池是一个很重要的概念

为什么要使用线程池

线程池可以更好地控制执行线程的数量。

线程的创建和销毁都会消耗系统开销的,因此为了防止线程不断地创建、销毁,提出线程池的概念,线程工作完之后并不会销毁,而是回到线程池,等待接受新的任务,减少系统创建、销毁线程的开销。

线程并不是越多越好,服务器的承受力是有限的,大量的线程会消耗过多的内存,导致服务器挂掉,利用线程池可以更好地管理线程数量。

线程池的相关接口和方法总结

Executor

executor是Java中线程池的顶级接口,有一个接口方法execute。严格来说,Executor和线程池没多大关系,它只是线程执行的工具。

void execute(Runnable command);

ExecutorService

这个接口才是线程池真正的老大,它继承了Executor接口的线程执行方法,同时扩充了一些批量操作线程的操作,完成了线程池的初步功能。下面学习一下扩充的方法:

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit) throws InterruptedException;

invokeAll方法,个人理解是可以批量开启一些线程,且线程之间不会相互影响,可以通过Future来完成对每个线程的操作。调用该方法的线程会阻塞,直到tasks全部执行完成(正常完成/异常退出),如果线程在等待invokeAll执行的过程中被中断,那么线程池就会终止所有正在被执行的任务,并抛出异常。
当然,我们也可以为其设置一个超时时间,超时之后,还没有完成的任务会被取消,在超时期之前,所有的线程(不管是否完成),其状态都不是取消态,即Future.isCancelled() = false;
可以大致理解为: 通过invokeAll方法提交的批量线程,要一起开始,一起结束(好兄弟,共进退)。


 <T> T invokeAny(Collection<? extends Callable<T>> tasks)  throws InterruptedException, ExecutionException;
 <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

相比于invokeAll共进退的友好,invokeAny要比较残忍一些,invokeAny同样是提交一批线程,但是,其中任何一个线程执行完毕,就会终止其他线程。当然,我们可以为其设置超时时间,超时之后如果没有线程执行完,会抛出异常。
invokeAny就像赛跑,任何线程(跑到终点),其他线程就已经输了(没有拿到第一),不会再让你跑下去,千军万马,只活一个。


//终止线程池,队列中的线程不执行,但是执行中的线程可以继续执行完毕
void shutdown();
//终止线程池,队列中的线程不执行,而且执行中的线程也立刻停止
List<Runnable> shutdownNow();
//调用了上面两种shutdown相关方法,isShutdown会返回true
boolean isShutdown();
//当线程池中的线程都停止时,isTerminated返回true
boolean isTerminated();

shutdown和shutdownNow的根本区别在于对 执行中线程的处理方式。

调用了shutdown或shutdownNow,线程池就会进入shutdown状态,当所有线程都停止(其实就是调用shutdown,执行中的线程也执行完停止)后,线程池进入终结状态,即Terminated。


<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);

这三个submit方法,都用来在线程池中提交一个任务,只不过是参数的区别,callable类型或Runnable类型。

Executor和ExecutorService都是接口,方法的真正实现是在下面的类中

AbstractExecutorService

AbstractExecutorService是一个抽象类,其实现了ExecutorService中的方法。其中三种不同的submit方法,我们知道其参数可以分为callable和Runnable两种,在AbstractExecutorService实现时,会将Runnable参数也转化为callable类型的参数,这个转换方式使用了适配器模式,日后学习之后加以补充。

另外,AbstractExecutorService并没有实现顶层接口Executor中的execute方法,因此它必须是一个抽象类,execute方法是在其实现类ThreadPoolExecutor中实现的。

ThreadPoolExecutor

ThreadPoolExecutor是AbstractExecutorService这个抽象类的实现类,包括了已经实现了的ExecutorService中的方法,和新实现的Executor中的execute方法。

如何创建线程池

学习了上面,线程池好香啊,那么怎么用呢

五种线程池

Java通过Executors提供五种线程池,可以通过Executors的静态方法来创建不同的线程池,分别为:

//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService executorService = Executors.newCachedThreadPool();
//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executorService = Executors.newFixedThreadPool();
//创建一个定长线程池,支持定时及周期性任务执行。
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool();
//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建持有足够线程的线程池来并行,通过使用多个队列减少竞争,不传参数,则默认设定为cpu的数量
ExecutorService executorService = Executors.newWorkStealingPool();

以下内容目前还不能实践理解,先行记录。。。。。

五种线程池的适应场景

CachedThreadPool:用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。
FixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制。
SingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程活动的场景。
ScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
WorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值