Java并发编程从入门到精通 - 第6章:线程池

Posted kehuaihan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程从入门到精通 - 第6章:线程池相关的知识,希望对你有一定的参考价值。

1、什么是线程池(为什么使用线程池):
2、Executor框架介绍:
  Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭(使用该框架来创建线程池),可以简化并发编程的操作;
  Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等;
  ExecutorService接口继承自Executor接口,提供了更丰富的实现多线程的方法,一般用该接口来实现和管理多线程;
  ExecutorService的生命周期包括三种状态:运行、关闭、终止;创建后便进入运行状态;当调用了shutdown()方法时,便进入关闭状态,此时意味着      ExecutorService不再接受新的任务,但它还在执行已经提交了的任务;当所有已经提交了的任务执行完后,便到达终止状态;如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可;
  更详细描述看:http://blog.csdn.net/ns_code/article/details/17465497

技术分享图片
 1 /**
 2  * Executor执行Runnable任务
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class ExecutorsTest01
10 {
11     public static void main(String[] args)
12     {
13         // ExecutorService executorService = Executors.newCachedThreadPool();
14         // ExecutorService executorService = Executors.newFixedThreadPool(4);
15         ExecutorService executorService = Executors.newSingleThreadExecutor();
16         
17         // 创建5个任务并执行
18         for(int i=0;i<5;i++)
19         {
20             executorService.execute(new RunnableTest2());
21         }
22         
23         // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务
24         executorService.shutdown();
25     }
26 }
27 
28 class RunnableTest2 implements Runnable
29 {
30     // 具体的业务逻辑;一旦RunnableTest2实例对象传给ExecutorService的execute方法, 则该方法(任务)自动在一个线程上执行
31     @Override
32     public void run()
33     {
34         System.out.println(Thread.currentThread().getName() + "线程被调用了");
35     }
36     
37 }
Executor执行Runnable任务
技术分享图片
 1 /**
 2  * Executor执行Callable任务
 3  */
 4 package thread05;
 5 
 6 import java.util.ArrayList;
 7 import java.util.List;
 8 import java.util.concurrent.Callable;
 9 import java.util.concurrent.ExecutionException;
10 import java.util.concurrent.ExecutorService;
11 import java.util.concurrent.Executors;
12 import java.util.concurrent.Future;
13 
14 public class ExecutorsTest02
15 {
16     public static void main(String[] args)
17     {
18         ExecutorService executorService = Executors.newCachedThreadPool();
19         List<Future<String>> futureList = new ArrayList<Future<String>>();
20         
21         // 创建10个任务并执行
22         for(int i=0;i<10;i++)
23         {
24             // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
25             Future<String> future = executorService.submit(new CallableTest2(i + ""));
26             // 将任务执行结果存储到List中
27             futureList.add(future);
28         }
29         
30         // 遍历任务的结果 
31         for(Future<String> f : futureList)
32         {
33             try
34             {
35                 // Future返回如果没有完成,则一直循环等待,直到Future返回完成
36                 while(!f.isDone());
37                 // 打印各个线程(任务)执行的结果 
38                 System.out.println(f.get());
39             }
40             catch (InterruptedException | ExecutionException e)
41             {
42                 e.printStackTrace();
43             }
44             finally
45             {
46                 // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务
47                 executorService.shutdown();
48             }
49         }
50         
51     }
52 }
53 
54 class CallableTest2 implements Callable<String>
55 {
56     private String id;
57     
58     public CallableTest2(String id)
59     {
60         this.id = id;
61     }
62 
63     // 具体的业务逻辑;一旦CallableTest2实例对象传给ExecutorService的submit方法, 则该方法(任务)自动在一个线程上执行
64     @Override
65     public String call() throws Exception
66     {
67         System.out.println("call()方法被自动调用,当前线程名称" + Thread.currentThread().getName());
68         return "call()方法被自动调用,任务的返回值:任务id:" + id + ",执行任务的线程名称:" + Thread.currentThread().getName();
69     }
70     
71 }
Executor执行Callable任务

3、三种生成常用线程池的静态工厂方法(三种自带的线程池):
  newSingleThreadExecutor(SingleThreadExecutor);
  newCachedThreadPool(CachedThreadPool);
  newFixedThreadPool(FixedThreadPool);
4、newSingleThreadExecutor的使用:
  创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务;如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它;此线程池保证所有任务的执行顺序按照任务的提交顺序执行;
  适用场景:适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景;

技术分享图片
 1 /**
 2  * newSingleThreadExecutor的使用
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class NewSingleThreadExecutorTest01
10 {
11     public static void main(String[] args)
12     {
13         ExecutorService executorService = Executors.newSingleThreadExecutor();
14         
15         for(int i=0;i<5;i++)
16         {
17             final int no = i;
18             
19             // 生成一条任务
20             Runnable runnable = new Runnable()
21             {
22                 @Override
23                 public void run()
24                 {
25                     try
26                     {
27                         System.out.println("into:" + no);
28                         Thread.sleep(1000);
29                         System.out.println("end:" + no);
30                     } 
31                     catch (InterruptedException e)
32                     {
33                         e.printStackTrace();
34                     }
35                 }
36             };
37             
38             // 将任务放到线程池中进行执行
39             executorService.execute(runnable);
40         }
41         
42         // 所有任务执行完毕之后,关闭线程池
43         executorService.shutdown();
44         
45         System.out.println("Thread Main End!");
46     }
47 }
newSingleThreadExecutor的使用

5、newCachedThreadPool的使用:
  创建一个缓存池大小可根据需要伸缩的线程池,但是在以前构造的线程可用时可重用它们;对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能;调用execute将重用以前构造的线程(如果线程可用);如果现有线程没有可用的,则创建一个新线程并添加到池中;终止并从缓存中移除那些已有60s未被使用的线程;因此,长时间保持空闲的线程池不会使用任何资源;
  适用场景:是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;

技术分享图片
 1 /**
 2  * newCachedThreadPool的使用
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class NewCachedThreadPoolTest01
10 {
11     public static void main(String[] args)
12     {
13         ExecutorService executorService = Executors.newCachedThreadPool();
14         
15         for(int i=0;i<10;i++)
16         {
17             final int no = i;
18             
19             Runnable runnable = new Runnable()
20             {
21                 @Override
22                 public void run()
23                 {
24                     try
25                     {
26                         System.out.println("into:" + no);
27                         Thread.sleep(1000L);
28                         System.out.println("end:" + no);
29                     } 
30                     catch (InterruptedException e)
31                     {
32                         e.printStackTrace();
33                     }
34                 }
35             };
36             
37             executorService.execute(runnable);
38         }
39         
40         executorService.shutdown();
41         
42         System.out.println("Thread Main End!");
43     }
44 }
newCachedThreadPool的使用

6、newFixedThreadPool的使用:
  创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程;在任意点,在大多数nThreads线程会处于处理任务的活动状态;如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待;如果在关闭前的执行期间而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要);在某个线程被显式地关闭之前,池中的线程将一直存在;
  适用场景:适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景;适用于负载比较重的服务器;

技术分享图片
 1 /**
 2  * newFixedThreadPool的使用
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class NewFixedThreadPool
10 {
11     public static void main(String[] args)
12     {
13         ExecutorService executorService = Executors.newFixedThreadPool(5);
14         
15         for(int i=0;i<20;i++)
16         {
17             final int no = i;
18             
19             Runnable runnable = new Runnable()
20             {
21                 @Override
22                 public void run()
23                 {
24                     try
25                     {
26                         System.out.println("into:" + no);
27                         Thread.sleep(1000L);
28                         System.out.println("end:" + no);
29                     } 
30                     catch (InterruptedException e)
31                     {
32                         e.printStackTrace();
33                     }
34                 }
35             };
36             
37             executorService.execute(runnable);
38         }
39         
40         executorService.shutdown();
41         
42         System.out.println("Thread Main End!");
43     }
44 }
newFixedThreadPool的使用

7、线程池的好处;
7.1、合理利用线程池能够带来4个好处:
(1)、降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗;
(2)、提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行;
(3)、提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控;
(4)、防止服务器过载,形成内存溢出,或者CPU耗尽;
7.2、线程池技术提高服务器程序的性能:
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力;但如果对多线程应用不当,会增加对单个任务的处理时间;
7.3、线程池的应用范围:
(1)、需要大量的线程来完成任务,且完成任务的时间比较短:Web服务器完成网页请求这样的任务,使用线程池技术是非常合适的;因为单个任务小,而任务数量巨大;
8、线程池的工作机制及其原理:
9、自定义线程池和ExecutorService:

技术分享图片
 1 /**
 2  * 自定义线程池
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ArrayBlockingQueue;
 7 import java.util.concurrent.BlockingQueue;
 8 import java.util.concurrent.ThreadPoolExecutor;
 9 import java.util.concurrent.TimeUnit;
10 
11 public class ThreadPoolTest01
12 {
13     public static void main(String[] args)
14     {
15         // 创建等待队列 
16         BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(20);
17         // 创建线程池,池中保存的线程数为3,允许的最大线程数为5  
18         ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 50, TimeUnit.MILLISECONDS,bq);
19         
20         // 创建七个任务
21         Runnable r1 = new RunnableTest3();
22         Runnable r2 = new RunnableTest3();
23         Runnable r3 = new RunnableTest3();
24         Runnable r4 = new RunnableTest3();
25         Runnable r5 = new RunnableTest3();
26         Runnable r6 = new RunnableTest3();
27         Runnable r7 = new RunnableTest3();
28         
29         // 每个任务会在一个线程上执行
30         pool.execute(r1);
31         pool.execute(r2);
32         pool.execute(r3);
33         pool.execute(r4);
34         pool.execute(r5);
35         pool.execute(r6);
36         pool.execute(r7);
37         
38         // 关闭线程池 
39         pool.shutdown();
40     }
41 }
42 
43 class RunnableTest3 implements Runnable
44 {
45     @Override
46     public void run()
47     {
48         System.out.println(Thread.currentThread().getName() + "正在执行...");
49         
50         try
51         {
52             Thread.sleep(1000);
53         }
54         catch (InterruptedException e)
55         {
56             e.printStackTrace();
57         }
58     }
59     
60 }
自定义线程池

10、线程池在工作中的错误使用:
(1)、分不清线程池是单例还是多对象:线程池一定要在合理的单例模式下才有效;就是说不要多次创建线程池;
(2)、线程池数量设置很大,请求过载:
(3)、注意死锁问题:































以上是关于Java并发编程从入门到精通 - 第6章:线程池的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程从入门到精通 - 第3章:Thread安全

Java并发编程从入门到精通 - 第2章:认识Thread

Java并发编程从入门到精通-总纲

Java并发编程从入门到精通 张振华.Jack --我的书

Java并发编程从入门到精通 - 第7章:Fork/Join框架

Java 从入门到精通(第4版)第6章 数组