首页 > 编程语言 >java线程池

java线程池

时间:2023-05-29 13:03:58浏览次数:43  
标签:java 任务 线程 Executor new 执行 public


1.JDK中的Executor框架是基于生产者-消费者模式的线程池,提交任务的线程是生产者,执行任务的线程是消费者。

Executor线程池可以用于异步任务执行,而且支持很多不同类型任务执行策略,同时为任务提交和任务执行之间的解耦提供了标准方法。

Executor线程池支持如下三种线程执行策略:

(1).顺序执行:

类似于单线程顺序执行任务,优点是实现简单;缺点是扩展性受限,执行效率低下,例子代码如下:

 


1. public class WithinThreadExecutor implements Executor{  
2.     public void execute(Runnable r){  
3.         r.run();  
4.     }  
5. }



 

(2)每请求每线程:

为每个请求创建一个新的线程,优点是可以并行处理;缺点是线程生命周期开销大,活动线程受内存资源、JVM以及操作系统的限制,当负载过大时响应性和吞吐量会下降严重,同时还会影响稳定性,例子代码如下:


1. public class ThreadPerTaskExecutor implements Executor{  
2.     public void execute(Runnable r){  
3.         new Thread(r).start();  
4.     }  
5. }


(3)线程池:

使用线程池可以重用已有线程,减少线程生命周期开销,同时可以调整活动线程数量,既可以确保足够的并发性,又避免过多线程相互竞争资源,例子代码如下:


1. public class TaskExecutionWebServer {  
2.     private static final int NTHREADS = 100;  
3.     private static final ExecutorService exec = Executors  
4.             .newFixedThreadPool(NTHREADS);  
5.   
6.     public static void main(String[] args) throws IOException {  
7.         ServerSocket socket = new ServerSocket(80);  
8.         while (true) {  
9.             final Socket connection = socket.accept();  
10.             Runnable task = new Runnable() {  
11.                 public void run() {  
12.                     handleRequest(connection);  
13.                 }  
14.             };  
15.             exec.execute(task);  
16.         }  
17.     }  
18. }


2.Executor常用的创建线程池静态工厂方法:

(1).newFixedThreadPool:

创建一个定长的线程池,每当提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化,若一个线程由于非预期的异常而结束,线程池会补充一个新的线程。

(2).newCachedThreadPool:

创建一个可缓存的线程池,若当前线程池的长度超过了处理的需要时,它可以灵活地回收空闲的线程,当需求增加时,它可以灵活地添加新的线程,而并不会对池的长度做任何限制。

(3).newSingleThreadExecutor:

创建一个单线程化的executor,只创建唯一的工作者线程来执行任务,若这个线程异常结束,会有另一个取代它。Executor会保证任务依照任务队列所规定的顺序执行。

(4).newScheduledThreadPool:

创建一个支持定时的以及周期性执行的任务的定长线程池。

3.Executor的生命周期:

ExecutorService接口扩展了Executor,提供了以下用于生命周期管理的方法:

1. public interface ExecutorService extends Executor{  
2.     void shutdown();  
3.     list<Runnable> shutdownNow();  
4.     boolean isShutdown();  
5.     boolean isTerminated();  
6.     boolean awaitTermination(long timeout, TimeUnit unit) throws InterruputedException;  
7.     ......  
8. }


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的线程泄露:


1. public class OutOfTimer {  
2.     public static void main(String[] args) throws Exception {  
3.         Timer timer = new Timer();  
4.         timer.schedule(new ThrowTask(), 1);  
5.         TimeUnit.SECONDS.sleep(1);  
6.         timer.schedule(new ThrowTask(), 1);  
7.         TimeUnit.SECONDS.sleep(5);  
8.     }  
9.       
10.     static class ThrowTask extends TimerTask{  
11.         public void run(){  
12.             System.out.println("I'm invoked.");  
13.             throw new RuntimeException();  
14.         }  
15.     }  
16. }


上面代码运行后只会打印出一行I'm invoked.然后就抛出Timer already cancelled异常。

ScheduledExecutorService可以妥善地处理异常,避免线程泄露。

下面的例子代码演示ScheduledExecutorService在异常之后仍然可以继续运行:


1. public class OutOfScheduledExecutor {  
2.     public static void main(String[] args) throws Exception {  
3.         ScheduledExecutorService service =                      Executors.newScheduledThreadPool(1);  
4.         service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);  
5.         TimeUnit.SECONDS.sleep(1);  
6.         service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);  
7.         TimeUnit.SECONDS.sleep(5);  
8.         service.shutdown();  
9.     }  
10.       
11.     static class ThrowTask implements Runnable{  
12.         public void run(){  
13.             System.out.println("I'm invoked.");  
14.             throw new RuntimeException();  
15.         }  
16.     }  
17. }


上述的ScheduledExecutorService例子没有抛出,可以正常打印出两行I'm invoked.

5.Callable和Future:

Runnable是Executor框架常用的任务基本表达形式,但是其run方法不能返回一个值或者抛出受检查的异常。

Callable类似于Runnable,其call方法可以等待返回值,并为可能抛出的异常预先做好准备。

Future描述了任务的生命周期,并提供了相关的方法来获得任务的结果、取消任务以及检验任务是否已经完成或者被取消。ExecutorService中所有的submit方法都返回一个Future。

使用Runnable/Callable和Future可以提高任务的并行性,例子代码如下:


    1. public class FutureRender{  
    2.     private final ExecutorService executor = ......;  
    3.       
    4.     public void renderPage(CharSequence source){  
    5.         final List<ImageInfo> imageInfos = scanForImageInfo(source);  
    6.         Callable<List<ImageData>> task = new Callable<List<ImageData>>(){  
    7.             public list<ImageData> call(){  
    8.                 List<ImageData> result = new ArrayList<ImageData>();  
    9.                 for(ImageInfo imageInfo : imageInfos){  
    10.                     result.add(imageInfo.downloadImage());  
    11.                 }  
    12.                 return result;  
    13.             }  
    14.         };  
    15.         Future<List<ImageData>> future = executor.submit(task);  
    16.         renderText(source);  
    17.         try{  
    18.             List<ImageData> imageDatas = future.get();  
    19.             for(ImageData data : imageDatas){  
    20.                 renderImage(data);  
    21.             }  
    22.         }catch(InterruptedException e){  
    23.             Thread.currentThread().interrupt();  
    24.             future.cancel(true);  
    25.         }catch(ExecutionException e){  
    26.             throw launderThrowable(e.getCause());  
    27.         }  
    28.     }  
    29. }



    注意:只有大量相互独立且同类的任务进行并发处理时,会将程序的任务量分配到不同的任务中,才能正在获得并发性能的提高;而对异类任务的并发处理则会因为任务协调的开销,不一定能获得性能的提高。

    6.CompletionService介绍:

    CompletionService整合了Executor与BlockingQueue的功能,可以将一个批处理任务提交给给它执行,然后返回一个包含每个任务执行结果的QueueingFuture队列,通过调用队列的take和poll方法,可以获得包含每个任务执行结果的Future。

    CompletionService的例子代码如下:


    1. public class CompletionServiceRender {  
    2.     private final ExecutorService executor;  
    3.   
    4.     public CompletionServiceRender(ExecutorService executor) {  
    5.         This.executor = executor;  
    6.     }  
    7.   
    8.     public void renderPage(CharSequence source) {  
    9.         final List<ImageInfo> imageInfos = scanForImageInfo(source);  
    10.         CompletionService<ImageData> service = new ExecutorCompletionService<ImageData>(  
    11.                 executor);  
    12.         for (final ImageInfo imageInfo : imageInfos) {  
    13.             service.submit(new Callable<ImageData>() {  
    14.                 public ImageData call() {  
    15.                     return imageInfo.downloadImage();  
    16.                 }  
    17.             });  
    18.         }  
    19.         renderText(source);  
    20.         try {  
    21.             for (int i = 0; i < imageInfos.size(); i++) {  
    22.                 Future<ImageDate> f = service.take();  
    23.                 ImageData data = f.get();  
    24.                 renderImage(data);  
    25.             }  
    26.         } catch (InterruptedException e) {  
    27.             Thread.currentThread().interrupt();  
    28.         } catch (ExecutionException e) {  
    29.             throw launderThrowable(e.getCause());  
    30.         }  
    31.     }  
    32. }



    7.线程的取消和关闭:

    对于非后台线程,如果取消和关闭不当会导致阻塞JVM无法正常关闭,Java提供了一个协作的中断机制使一个线程能够要求另一个线程停止当前工作。

    Java中常用的取消和关闭策略如下:

    (1).非阻塞方法:

    使用volatile域保存取消状态,在每次操作时检测该状态。

    (2).阻塞方法:

    线程可能永远不会检测取消标志,因此使用volatile域保存取消状态的方案不可行,需要使用线程中断。

    线程中断是一个协作机制,一个线程给另一个线程发送信号,通知它在下一个方便时刻(通常称为取消点)停止正在做的工作,去做其他事情。

    每个线程都有一个boolean类型的中断状态,在中断的时候该中断状态被设置为true,线程中断相关的方法如下:


    1. public class Thread{  
    2.     //中断目标线程  
    3.     public void interrupt(){......}  
    4.   
    5.     //返回目标线程的中断状态  
    6.     public boolean isInterrupted(){......}  
    7.   
    8.     //清除当前线程的中断状态,并返回它之前的值  
    9.     public static boolean interrupted(){......}  
    10.   
    11.     ......  
    12. }



    特定阻塞库类的方法都支持中断,中断通常是实现线程取消最明智的选择。

    (3).Executor和Future:

    Executor线程池可以使用shutdown和shutdownNow方法来关闭线程池。

    Future可以使用cancel方法取消任务。

    (4).JVM关闭钩子:

    在JVM正常关闭时,可以执行使用Runtime.addShutdownHook注册的尚未开始执行的线程(关闭钩子),例子代码如下:


    1. public void start(){  
    2.     Runtime.getRuntime().addShutdownHook(new Thread(){  
    3.         public void run(){  
    4.             try{  
    5.                 LogService.this.stop();  
    6.             }catch(InterruptedException ignore){  
    7.             }  
    8.         }  
    9.     });  
    10. }

    JVM关闭钩子全部是并发执行,因此必须是线程安全,访问共享数据必须要同步,同时小心避免死锁。

    JVM关闭钩子常用于服务或应用程序的清理,或者清除OS不能自动清除的资源。

    标签:java,任务,线程,Executor,new,执行,public
    From: https://blog.51cto.com/u_16131764/6370112

    相关文章

    • javascript常用正则表达式
      javascript身份证号验证正则1.//这个可以验证15位和18位的身份证,并且包含生日和校验位的验证。2.//如果有兴趣,还可以加上身份证所在地的验证,就是前6位有些数字合法有些数字不合法。3.4.function5.num=num.toUpperCase();6.//身份证号码为15位或者18......
    • Javascript编程风格
       所谓"编程风格"(programmingstyle),指的是编写代码的样式规则。不同的程序员,往往有不同的编程风格。 有人说,编译器的规范叫做"语法规则"(grammar),这是程序员必须遵守的;而编译器忽略的部分,就叫"编程风格"(programmingstyle),这是程序员可以自由选择的。这种说法不完全正确,程序员固然可......
    • java中fail-fast 和 fail-safe的区别
       在我们详细讨论这两种机制的区别之前,首先得先了解并发修改。1.什么是并发修改?当一个或多个线程正在遍历一个集合Collection,此时另一个线程修改了这个集合的内容(添加,删除或者修改)。这就是并发修改。 2.什么是fail-fast机制?fail-fast机制在遍历一个集合时,当集合结构被修改,会抛......
    • java socket
           对于JavaSocket编程而言,有两个概念,一个是ServerSocket,一个是Socket。服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个......
    • Java中的堆和栈的区别
      当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更糟糕的是,Java中存在栈这样一个后进先出(LastInFirstOut)的顺序的数据结构,这就是java.util.Stack。这种情况下,不免让很多人更加......
    • java二进制运算
      //对于原码,反码,补码而言,需要注意以下几点://(1) 二进制的最高位是符号位,0表示正数,1表示负数;//(2) 正数的原码,反码,补码都一样;//(3) 负数的原码=对应正数的二进制原码,最高位设为1;//(4) 负数的反码=它的原码符号位不变,其他位取反;//(5) 负数的补码=它的......
    • 压栈思想计算Java运算表达式
           栈的规则是先进后出。利用压栈的思想来计算四则运算表达式是这样的:我们给定两个栈,一个用来存放数字、一个用来存放对应的操作符。假定我们有一个给定的四则运算表达式a+b+c/d*(e+f)-d*a,那我们先把这个表达式拆分成一个个的数字或者是运算符、或者就是括号了。然后我们......
    • java IO
      packagecom.jaeson.javastudy;importjava.io.*;importjava.util.zip.*;publicclassIOTest{ //跨平台的文件分割符 staticvoidseparator(){ System.out.println(File.separator); //\System.out.println(File.pathSeparator); //; } //创建文件,创......
    • Java内存溢出和内存泄露
      一、为什么要了解内存泄露和内存溢出? 1、内存泄露一般是代码设计存在缺陷导致的,通过了解内存泄露的场景,可以避免不必要的内存溢出和提高自己的代码编写水平;2、通过了解内存溢出的几种常见情况,可以在出现内存溢出的时候快速的定位问题的位置,缩短解决故障的时间。 二、基本概念 内......
    • Java 内存模型及GC原理
      一个优秀Java程序员,必须了解Java内存模型、GC工作原理,以及如何优化GC的性能、与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统、实时系统等,只有全面提升内存的管理效率,才能提高整个应用程序的性能。本文将从JVM内存模型、GC工作原理,以及GC的几个关键问题进行探讨,从G......