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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
3、三种生成常用线程池的静态工厂方法(三种自带的线程池):
newSingleThreadExecutor(SingleThreadExecutor);
newCachedThreadPool(CachedThreadPool);
newFixedThreadPool(FixedThreadPool);
4、newSingleThreadExecutor的使用:
创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务;如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它;此线程池保证所有任务的执行顺序按照任务的提交顺序执行;
适用场景:适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
5、newCachedThreadPool的使用:
创建一个缓存池大小可根据需要伸缩的线程池,但是在以前构造的线程可用时可重用它们;对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能;调用execute将重用以前构造的线程(如果线程可用);如果现有线程没有可用的,则创建一个新线程并添加到池中;终止并从缓存中移除那些已有60s未被使用的线程;因此,长时间保持空闲的线程池不会使用任何资源;
适用场景:是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
6、newFixedThreadPool的使用:
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程;在任意点,在大多数nThreads线程会处于处理任务的活动状态;如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待;如果在关闭前的执行期间而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要);在某个线程被显式地关闭之前,池中的线程将一直存在;
适用场景:适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景;适用于负载比较重的服务器;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
7、线程池的好处;
7.1、合理利用线程池能够带来4个好处:
(1)、降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗;
(2)、提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行;
(3)、提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控;
(4)、防止服务器过载,形成内存溢出,或者CPU耗尽;
7.2、线程池技术提高服务器程序的性能:
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力;但如果对多线程应用不当,会增加对单个任务的处理时间;
7.3、线程池的应用范围:
(1)、需要大量的线程来完成任务,且完成任务的时间比较短:Web服务器完成网页请求这样的任务,使用线程池技术是非常合适的;因为单个任务小,而任务数量巨大;
8、线程池的工作机制及其原理:
9、自定义线程池和ExecutorService:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)、注意死锁问题: