1、基本介绍:
1.1、线程池好处:
- 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率:
- 线程池缓存线程,可用已有的闲置线程来执行新任务。
- 线程并发数量过多,抢占系统资源从而导致阻塞:
- 我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况。
- 运用线程池能有效的控制线程最大并发数,避免以上的问题。
- 对线程进行一些简单的管理:
- 比如:延时执行、定时循环执行的策略等。
2、Executor接口类:
Executor接口:
一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command)。ExecutorService接口:
是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法。AbstractExecutorService抽象类:
ExecutorService执行方法的默认实现 。ThreadPoolExecutor类:
线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象。ScheduledExecutorService接口:
一个可定时调度任务的接口 。ScheduledThreadPoolExecutor类:
ScheduledExecutorService的实现,一个可定时调度任务的线程池。
3、ExecutorService接口类:
3.1、通过Executors工厂类创建ExecutorService:
3.1.1、Executors工厂类:
- Executors只是一个工厂类,它所有的方法返回的都是ThreadPoolExecutor、ScheduledThreadPoolExecutor这两个类的实例。
- Executors工厂类可以帮助我们很方便的创建各种类型ExecutorService线程池:
3.1.2、线程池种类:
newCachedThreadPool:
创建可缓存的线程池,如果线程池中的线程在60秒未被使用就将被移除,在执行新的任务时,当线程池中有之前创建的可用线程就重用可用线程,否则就新建一条线程。
newFixedThreadPool:
创建可重用且固定线程数的线程池,如果线程池中的所有线程都处于活动状态,此时再提交任务就在队列中等待,直到有可用线程;如果线程池中的某个线程由于异常而结束时,线程池就会再补充一条新线程。
newScheduledThreadPool:
创建一个可延迟执行或定期执行的线程池。
newSingleThreadExecutor:
创建一个单线程的Executor,如果该线程因为异常而结束就新建一条线程来继续执行后续的任务。
3.2、使用ExecutorService:
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
3.3、执行启动ExecutorService:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)
3.3.1、execute(Runnable):
这个方法接收一个Runnable实例,并且异步的执行
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
注意:
这个方法有个问题,就是没有办法获知task的执行结果。如果我们想获得task的执行结果,我们可以传入一个Callable的实例。
3.3.2、submit(Runnable):
submit(Runnable)和execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕。
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
future.get();
注意:
如果任务执行完成,future.get()方法会返回一个null。注意,future.get()方法会产生阻塞。
3.3.3、submit(Callable):
submit(Callable)和submit(Runnable)类似,也会返回一个Future对象,但是除此之外,submit(Callable)接收的是一个Callable的实现,Callable接口中的call()方法有一个返回值,可以返回任务的执行结果,而Runnable接口中的run()方法是void的,没有返回值。
3.3.4、invokeAny(…):
invokeAny(...)方法接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();
注意:
每次执行都会返回一个结果,并且返回的结果是变化的,可能会返回“Task2”也可是“Task1”或者其它。
3.3.5、invokeAll(…):
invokeAll(...)与 invokeAny(...)类似也是接收一个Callable集合,但是前者执行之后会返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future : futures) {
System.out.println("future.get = " + future.get());
}
executorService.shutdown();
3.4、关闭ExecutorService:
- 当我们使用完成ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态。举个例子,如果应用程序是通过main()方法启动的,在这个main()退出之后,如果应用程序中的ExecutorService没有关闭,这个应用将一直运行。之所以会出现这种情况,是因为ExecutorService中运行的线程会阻止JVM关闭。
- 如果要关闭ExecutorService中执行的线程,我们可以调用
ExecutorService.shutdown()
方法。在调用shutdown()
方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。 - 如果我们想立即关闭ExecutorService,我们可以调用
ExecutorService.shutdownNow()
方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。
4、ThreadPoolExecutor:
4.1、基本介绍:
ThreadPoolExecutor是ExecutorService的一个实现类,也是java中最常用的线程池类。ThreadPoolExecutor内部维持了一个线程池,可以执行给定的任务。
4.2、具体使用:
4.2.1、构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { ... }
参数说明:
- corePoolSize:
核心线程数,如果运行的线程少于corePoolSize,则创建新线程来执行新任务,即使线程池中的其他线程是空闲的。
- maximumPoolSize:
最大线程数,可允许创建的线程数,corePoolSize和maximumPoolSize设置的边界自动调整池大小。
• corePoolSize < 运行的线程数 < maximumPoolSize:仅当队列满时才创建新线程,如果队列不满,有空闲线程时就重用空闲线程。
• corePoolSize = 运行的线程数 = maximumPoolSize:创建固定大小的线程池。
- keepAliveTime:
如果线程数多于corePoolSize,则这些多余的线程的空闲时间超过keepAliveTime时将被终止。
- unit:
keepAliveTime参数的时间单位,为TimeUnit类中的几个静态常量。
- workQueue:缓冲队列,默认是LinkedBlockingQueue<Runnable>
保存任务的阻塞队列,与线程池的大小有关:
§ 当运行的线程数少于corePoolSize时,在有新任务时直接创建新线程来执行任务而无需再进队列。
§ 当运行的线程数等于或多于corePoolSize,在有新任务添加时则选加入队列,不直接创建线程,当队列满时,再有新任务时就创建新线程。
- threadFactory:
使用ThreadFactory创建新线程,默认使用defaultThreadFactory创建线程。
- handler:
定义处理被拒绝任务的策略,主要有四种类型:
ThreadPoolExecutor.AbortPolicy()(系统默认):任务被拒绝时抛出java.util.concurrent.RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy():当抛出RejectedExecutionException异常时,会调用rejectedExecution方法
ThreadPoolExecutor.DiscardOldestPolicy():抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy():抛弃当前的任务
4.2.2、线程池中线程数量控制:
ThreadPoolExecutor线程池中的线程数量是可变的,具体线程的分配方式是,当一个任务被添加到线程池:
1. 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2. 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列,不需要创建新线程。
3. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,创建新线程来处理被添加的任务。
4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
5. 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。
5、ScheduledThreadPoolExecutor:
5.1、基本介绍:
ScheduledThreadPoolExecutor是ExecutorService的另一个实现类,ScheduledThreadPoolExecutor直接继承自ScheduledExecutorService。
5.1.1、ScheduledExecutorService接口:
它继承了ExecutorService,它的最主要的功能就是可以对其中的任务进行调度,比如延迟执行、定时执行等等。
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public ScheduledFuture<?> schedule(Callable command, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay, long period, TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit);
}
schedule (Runnable task, long delay, TimeUnit timeunit):
这个方法的意思是在指定延迟delay之后运行task。这个方法有个问题,就是没有办法获知task的执行结果。schedule (Callable task, long delay, TimeUnit timeunit):
这个方法与schedule (Runnable task)类似,也是在指定延迟之后运行task,不过它接收的是一个Callable实例,此方法会返回一个ScheduleFuture对象,通过ScheduleFuture我们可以取消一个未执行的task,也可以获得这个task的执行结果。scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit):
这个方法的作用是周期性的调度task执行。task第一次执行的延迟根据initialDelay参数确定,以后每一次执行都间隔period时长。
如果task的执行时间大于定义的period,那么下一个线程将在当前线程完成之后再执行。整个调度保证不会出现一个以上任务同时执行。scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit):
scheduleWithFixedDelay的参数和scheduleAtFixedRate参数完全一致,它们的不同之处在于对period调度周期的解释。
在scheduleAtFixedRate中,period指的两个任务开始执行的时间间隔,也就是当前任务的开始执行时间和下个任务的开始执行时间之间的间隔。
而在scheduleWithFixedDelay中,period指的当前任务的结束执行时间到下个任务的开始执行时间。
参考文章: https://www.cnblogs.com/MOBIN/p/5436482.html
https://www.jianshu.com/p/210eab345423