1.JDK中的Executor框架是基于生产者-消费者模式的线程池,提交任务的线程是生产者,执行任务的线程是消费者。
Executor线程池可以用于异步任务执行,而且支持很多不同类型任务执行策略,同时为任务提交和任务执行之间的解耦提供了标准方法。
Executor线程池支持如下三种线程执行策略:
(1).顺序执行:
类似于单线程顺序执行任务,优点是实现简单;缺点是扩展性受限,执行效率低下,例子代码如下:
- public class WithinThreadExecutor implements Executor{
- public void execute(Runnable r){
- r.run();
- }
- }
(2)每请求每线程:
为每个请求创建一个新的线程,优点是可以并行处理;缺点是线程生命周期开销大,活动线程受内存资源、JVM以及操作系统的限制,当负载过大时响应性和吞吐量会下降严重,同时还会影响稳定性,例子代码如下:
- public class ThreadPerTaskExecutor implements Executor{
- public void execute(Runnable r){
- new Thread(r).start();
- }
- }
(3)线程池:
使用线程池可以重用已有线程,减少线程生命周期开销,同时可以调整活动线程数量,既可以确保足够的并发性,又避免过多线程相互竞争资源,例子代码如下:
- public class TaskExecutionWebServer {
- private static final int NTHREADS = 100;
- private static final ExecutorService exec = Executors
- .newFixedThreadPool(NTHREADS);
- public static void main(String[] args) throws IOException {
- ServerSocket socket = new ServerSocket(80);
- while (true) {
- final Socket connection = socket.accept();
- Runnable task = new Runnable() {
- public void run() {
- handleRequest(connection);
- }
- };
- exec.execute(task);
- }
- }
- }
2.Executor常用的创建线程池静态工厂方法:
(1).newFixedThreadPool:
创建一个定长的线程池,每当提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化,若一个线程由于非预期的异常而结束,线程池会补充一个新的线程。
(2).newCachedThreadPool:
创建一个可缓存的线程池,若当前线程池的长度超过了处理的需要时,它可以灵活地回收空闲的线程,当需求增加时,它可以灵活地添加新的线程,而并不会对池的长度做任何限制。
(3).newSingleThreadExecutor:
创建一个单线程化的executor,只创建唯一的工作者线程来执行任务,若这个线程异常结束,会有另一个取代它。Executor会保证任务依照任务队列所规定的顺序执行。
(4).newScheduledThreadPool:
创建一个支持定时的以及周期性执行的任务的定长线程池。
3.Executor的生命周期:
ExecutorService接口扩展了Executor,提供了以下用于生命周期管理的方法:
- public interface ExecutorService extends Executor{
- void shutdown();
- list<Runnable> shutdownNow();
- boolean isShutdown();
- boolean isTerminated();
- boolean awaitTermination(long timeout, TimeUnit unit) throws InterruputedException;
- ......
- }
ExecutorService接口暗示了Executor的生命周期有以下3中状态:
(1).运行状态:
ExecutorService最初创建后的初始状态是运行状态。
(2).关闭状态:
ExecutorService的sutdown方法会启动一个平缓的关闭过程,停止接收新任务,同时等待已提交的任务执行完成(包括尚未开始执行的任务)。
ExecutorService的sutdownNow方法会启动一个强制的关闭过程,尝试取消所有运行中的任务和排在队列中尚未开始执行的任务。
(3)终止状态:
一旦所有任务全部完成后,ExecutorService就会进入终止状态,通过调研ExecutorService的awaitTermination方法等待达到终止状态,也可以调用isTerminated来轮询是否达到终止状态。
4.Timer与ScheduledExecutorService:
在JDK1.5之前,经常使用Timer(开源的Quartz框架也可以)作为定时器管理任务的延迟或周期性执行,在JDK1.5引入了ScheduledExecutorService,使用线程池作为定时器管理任务的延迟或周期性执行,二者的区别如下:
(1).Timer对调度的支持是基于绝对时间的,不支持相对时间,因此任务对系统时钟的改变是敏感的;ScheduledExecutorService只支持相对时间。
(2).Timer只创建唯一的线程来执行所有的timer任务,若一个timer任务的执行很耗时,会导致其他的timer任务时效准确性问题,例如一个timer任务每10ms执行一次,而另一个timer任务每40ms执行一次,若按固定频率进行调度则重复出现的任务会在耗时的任务完成后快速联系地被调用4次,若按延迟进行调度则完全丢失4次调用。
ScheduledExecutorService可以提供多个线程来执行延迟或按固定频率执行的周期性任务,解决了Timer任务时效准确性问题。
(3).若Timer任务抛出未检查异常时,Timer将会被异常地终止,Timer也不会再重新恢复线程执行,它错误地认为整个Timer都被取消了,从而产生无法预料的线程泄露:所有已被安排但尚未执行的Timer任务永远不会再执行了,新的任务也不能被调度了。
下面的例子代码演示Timer的线程泄露:
- public class OutOfTimer {
- public static void main(String[] args) throws Exception {
- Timer timer = new Timer();
- timer.schedule(new ThrowTask(), 1);
- TimeUnit.SECONDS.sleep(1);
- timer.schedule(new ThrowTask(), 1);
- TimeUnit.SECONDS.sleep(5);
- }
- static class ThrowTask extends TimerTask{
- public void run(){
- System.out.println("I'm invoked.");
- throw new RuntimeException();
- }
- }
- }
上面代码运行后只会打印出一行I'm invoked.然后就抛出Timer already cancelled异常。
ScheduledExecutorService可以妥善地处理异常,避免线程泄露。
下面的例子代码演示ScheduledExecutorService在异常之后仍然可以继续运行:
- public class OutOfScheduledExecutor {
- public static void main(String[] args) throws Exception {
- ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
- service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);
- TimeUnit.SECONDS.sleep(1);
- service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);
- TimeUnit.SECONDS.sleep(5);
- service.shutdown();
- }
- static class ThrowTask implements Runnable{
- public void run(){
- System.out.println("I'm invoked.");
- throw new RuntimeException();
- }
- }
- }
上述的ScheduledExecutorService例子没有抛出,可以正常打印出两行I'm invoked.
5.Callable和Future:
Runnable是Executor框架常用的任务基本表达形式,但是其run方法不能返回一个值或者抛出受检查的异常。
Callable类似于Runnable,其call方法可以等待返回值,并为可能抛出的异常预先做好准备。
Future描述了任务的生命周期,并提供了相关的方法来获得任务的结果、取消任务以及检验任务是否已经完成或者被取消。ExecutorService中所有的submit方法都返回一个Future。
使用Runnable/Callable和Future可以提高任务的并行性,例子代码如下:
- public class FutureRender{
- private final ExecutorService executor = ......;
- public void renderPage(CharSequence source){
- final List<ImageInfo> imageInfos = scanForImageInfo(source);
- Callable<List<ImageData>> task = new Callable<List<ImageData>>(){
- public list<ImageData> call(){
- List<ImageData> result = new ArrayList<ImageData>();
- for(ImageInfo imageInfo : imageInfos){
- result.add(imageInfo.downloadImage());
- }
- return result;
- }
- };
- Future<List<ImageData>> future = executor.submit(task);
- renderText(source);
- try{
- List<ImageData> imageDatas = future.get();
- for(ImageData data : imageDatas){
- renderImage(data);
- }
- }catch(InterruptedException e){
- Thread.currentThread().interrupt();
- future.cancel(true);
- }catch(ExecutionException e){
- throw launderThrowable(e.getCause());
- }
- }
- }
注意:只有大量相互独立且同类的任务进行并发处理时,会将程序的任务量分配到不同的任务中,才能正在获得并发性能的提高;而对异类任务的并发处理则会因为任务协调的开销,不一定能获得性能的提高。
6.CompletionService介绍:
CompletionService整合了Executor与BlockingQueue的功能,可以将一个批处理任务提交给给它执行,然后返回一个包含每个任务执行结果的QueueingFuture队列,通过调用队列的take和poll方法,可以获得包含每个任务执行结果的Future。
CompletionService的例子代码如下:
- public class CompletionServiceRender {
- private final ExecutorService executor;
- public CompletionServiceRender(ExecutorService executor) {
- This.executor = executor;
- }
- public void renderPage(CharSequence source) {
- final List<ImageInfo> imageInfos = scanForImageInfo(source);
- CompletionService<ImageData> service = new ExecutorCompletionService<ImageData>(
- executor);
- for (final ImageInfo imageInfo : imageInfos) {
- service.submit(new Callable<ImageData>() {
- public ImageData call() {
- return imageInfo.downloadImage();
- }
- });
- }
- renderText(source);
- try {
- for (int i = 0; i < imageInfos.size(); i++) {
- Future<ImageDate> f = service.take();
- ImageData data = f.get();
- renderImage(data);
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (ExecutionException e) {
- throw launderThrowable(e.getCause());
- }
- }
- }
7.线程的取消和关闭:
对于非后台线程,如果取消和关闭不当会导致阻塞JVM无法正常关闭,Java提供了一个协作的中断机制使一个线程能够要求另一个线程停止当前工作。
Java中常用的取消和关闭策略如下:
(1).非阻塞方法:
使用volatile域保存取消状态,在每次操作时检测该状态。
(2).阻塞方法:
线程可能永远不会检测取消标志,因此使用volatile域保存取消状态的方案不可行,需要使用线程中断。
线程中断是一个协作机制,一个线程给另一个线程发送信号,通知它在下一个方便时刻(通常称为取消点)停止正在做的工作,去做其他事情。
每个线程都有一个boolean类型的中断状态,在中断的时候该中断状态被设置为true,线程中断相关的方法如下:
- public class Thread{
- //中断目标线程
- public void interrupt(){......}
- //返回目标线程的中断状态
- public boolean isInterrupted(){......}
- //清除当前线程的中断状态,并返回它之前的值
- public static boolean interrupted(){......}
- ......
- }
特定阻塞库类的方法都支持中断,中断通常是实现线程取消最明智的选择。
(3).Executor和Future:
Executor线程池可以使用shutdown和shutdownNow方法来关闭线程池。
Future可以使用cancel方法取消任务。
(4).JVM关闭钩子:
在JVM正常关闭时,可以执行使用Runtime.addShutdownHook注册的尚未开始执行的线程(关闭钩子),例子代码如下:
- public void start(){
- Runtime.getRuntime().addShutdownHook(new Thread(){
- public void run(){
- try{
- LogService.this.stop();
- }catch(InterruptedException ignore){
- }
- }
- });
- }
JVM关闭钩子全部是并发执行,因此必须是线程安全,访问共享数据必须要同步,同时小心避免死锁。
JVM关闭钩子常用于服务或应用程序的清理,或者清除OS不能自动清除的资源。
相关推荐
java线程池使用后到底要关闭吗 java线程池是一种高效的并发编程技术,可以帮助开发者更好地管理线程资源,提高系统的性能和可靠性。然而,在使用java线程池时,一个常见的问题是:使用完线程池后到底要不要关闭?...
"Java 线程池完整代码解析" Java 线程池是 Java 语言中的一个重要概念,它允许开发者创建和管理多个线程,以提高程序的并发性和性能。下面是对给定文件的解析,包括 title、description、标签和部分内容的解析。 ...
Java线程池是一种高效管理线程的技术,它允许开发者预定义一组线程,根据任务的需要灵活调度,而不是每次需要执行任务时都创建新的线程。这种设计模式大大提高了系统的性能,减少了系统资源的消耗,特别是在高并发...
java线程池知识、
Java线程池是一种高效管理线程资源的工具,它能够帮助开发者有效地控制并调度线程,从而提升系统性能,减少系统资源的浪费。在Java中,`ExecutorService`接口是线程池的主要入口,它是`java.util.concurrent`包的一...
Java线程池是Java并发编程中的重要组件,它能够有效地管理和复用线程,从而提高程序的执行效率和降低资源消耗。在JDK 1.5版本之前,Java对线程池的支持非常有限,而在JDK 1.5之后,加入了java.util.concurrent包,...
### 自定义实现Java线程池 #### 一、概述 在深入探讨自定义Java线程池之前,我们先简要回顾一下线程池的基本概念及其重要性。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动...
Java线程池是Java并发编程中的重要组成部分,它在多线程和高并发场景下扮演着关键角色。本文将深入探讨Java线程池的源码分析,并对比不同类型的线程池,以帮助开发者更好地理解和利用这一强大的工具。 首先,我们要...
Java线程池是一种高效管理线程资源的工具,它的出现是为了应对多线程编程中频繁创建和销毁线程带来的性能开销以及资源消耗。在Java中,通过使用线程池,我们可以预先创建一定数量的线程,这些线程在空闲时可以被复用...
Java线程池是Java并发编程中的重要组成部分,它允许开发者管理多个线程并有效地调度任务。线程池通过ThreadPoolExecutor类实现,这是一个高度可配置的工具,能够根据具体需求定制线程的创建、管理和销毁策略。 ...
简单的线程池程序+中文文档 包结构: com.tangkai.threadpool --SimpleThread.java 工作线程 --TestThreadPool.java 程序入口 --ThreadPoolManager.java 线程池管理类
Java线程池是一种高级的多线程处理框架,它是Java并发编程中非常重要的一个组件。线程池的原理和实现涉及到操作系统调度、内存管理和并发控制等多个方面。理解线程池的工作原理有助于优化程序性能,避免过度创建和...
2.然后根据提示运行java命令执行示例程序,观看线程池的运行结果 目标:Java中多线程技术是一个难点,但是也是一个核心技术。因为Java本身就是一个多线程语言。本人目前在给46班讲授Swing的网络编程--使用Swing来...
### Java线程池详解 #### 一、线程与线程池的概念 在Java中,线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程(例如某个Java应用)至少有一个线程,如果线程...
Java线程池是一种高效管理并发任务的机制,它允许开发者预先配置一定数量的线程,以便在处理多个并发任务时能有效地复用这些线程,从而避免了频繁创建和销毁线程带来的开销。在Java中,`java.util.concurrent`包下的...
基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池...
Java线程池是一种高效利用系统资源、管理并发执行任务的机制。它的原理是通过预先创建一组线程,这些线程在任务到来时可以立即执行,而不是每次需要执行任务时都新建线程,从而降低了线程创建和销毁带来的开销。...
Java线程池是Java并发编程中的重要组成部分,它允许开发者高效地管理多个并发执行的线程,有效地控制系统的资源消耗,提高系统性能和稳定性。在Java中,`java.util.concurrent`包提供了`ExecutorService`接口及其...
讲述了java线程池的优点,参数,6种线程池的使用场景,线程池用到的handler,线程任务的提交方式等等。
本文所提及的基于Java线程池技术的数据爬虫设计与实现,不仅涉及到了数据爬虫的原理和架构,还包括了多线程编程的知识点,以及线程池技术在数据爬虫中的具体应用。 首先,数据爬虫的基本原理是模拟用户的点击行为,...