【本文转载自博主林炳文Evankaka原创文章http://blog.csdn.net/evankaka】
目录:
- 一Executors的API介绍
- 二几种不同的ExecutorService线程池对象
- 三线程池一些常用方法
- 四ThreadPoolExecutor技术内幕
- 五自定义线程池
摘要: 本文主要讲了Java当中的线程池的使用方法、注意事项及其实现源码实现原理,并辅以实例加以说明,对加深Java线程池的理解有很大的帮助。
首先,讲讲什么是线程池?照笔者的简单理解,其实就是一组线程实时处理休眠状态,等待唤醒执行。那么为什么要有线程池这个东西呢?可以从以下几个方面来考虑:
其一、减少在创建和销毁线程上所花的时间以及系统资源的开销 ,解决资源不足的问题;
其二、将当前任务与主线程隔离,能实现和主线程的异步执行,特别是很多可以分开重复执行的任务。但是,一味的开线程也不一定能带来性能上的优化,线池休眠也是要占用一定的内存空间,所以合理的选择线程池的大小也是有一定的依据。
为什么需要线程池?
基于以下几个原因在多线程应用程序中使用线程是必须的:
1. 线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。
2. 线程池节省了CLR 为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源。
3. 线程池根据当前在系统中运行的进程来优化线程时间片。
4. 线程池允许我们开启多个任务而不用为每个线程设置属性。
5. 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。
6. 线程池可以用来解决处理一个特定请求最大线程数量限制问题。
影响设计一个多线程应用程序的因素有:
1. 一个应用程序的响应时间。
2. 线程管理资源的分配。
3. 资源共享。
4. 线程同步。
Java类库提供了许多静态方法来创建一个线程池:
| newCachedThreadPool | 创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制 |
| newFixedThreadPool | 创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化 |
| newSingleThreadPoolExecutor | 创建一个单线程的Executor,确保任务对了,串行执行 |
| newScheduledThreadPool | 创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer |
小结一下:在线程池中执行任务比为每个任务分配一个线程优势更多,通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊线程创建和销毁产生的巨大的开销。当请求到达时,通常工作线程已经存在,提高了响应性;通过配置线程池的大小,可以创建足够多的线程使CPU达到忙碌状态,还可以防止线程太多耗尽计算机的资源。
创建线程池基本方法:
(1)定义线程类
class Handler implements Runnable { …… }
(2)建立ExecutorService线程池
ExecutorService executorService = Executors.newCachedThreadPool();
或者
int cpuNums = Runtime.getRuntime().availableProcessors(); //获取当前系统的CPU 数目 ExecutorService executorService =Executors.newFixedThreadPool(cpuNums * POOL_SIZE); //ExecutorService通常根据系统资源情况灵活定义线程池大小
CPU手动查看方式:任务管理器-->性能-->CPU使用记录(此处有几个小窗口就表示CPU核心数目的个数)
(3)调用线程池操作
循环操作,成为daemon,把新实例放入Executor池中
while(true){ executorService.execute(new Handler(socket)); // class Handler implements Runnable{ 或者 executorService.execute(createTask(i)); //private static Runnable createTask(final int taskID) }
二、几种不同的ExecutorService线程池对象
| 1.CachedThreadPool() | -缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse.如果没有,就建一个新的线程加入池中 -缓存型池子通常用于执行一些生存期很短的异步型任务 因此在一些面向连接的daemon型SERVER中用得不多。 -能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。 注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。 |
| 2.FixedThreadPool |
-newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程 |
| 3.ScheduledThreadPool | -调度型线程池 -这个池子里的线程可以按schedule依次delay执行,或周期执行 |
| 4.SingleThreadExecutor | -单例线程,任意时间池中只能有一个线程 -用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE) |
Java学习交流QQ群:603654340 我们一起学Java!
应用实例:
1.newCachedThreadPool
CachedThreadPool首先会按照需要创建足够多的线程来执行任务(Task)。随着程序执行的过程,有的线程执行完了任务,可以被重新循环使用时,才不再创建新的线程来执行任务。我们采用《Thinking In Java》中的例子来分析。客户端线程和线程池之间会有一个任务队列。当程序要关闭时,你需要注意两件事情:入队的这些任务的情况怎么样了以及正在运行的这个任务执行得如 何了。令人惊讶的是很多开发人员并没能正确地或者有意识地去关闭线程池。正确的方法有两种:一个是让所有的入队任务都执行完毕(shutdown()), 再就是舍弃这些任务(shutdownNow())——这完全取决于你。比如说如果我们提交了N多任务并且希望等它们都执行完后才返回的话,那么就使用 shutdown()。
例子:
1 package com.mmz.OtherTest; 2 3 import java.util.Date; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 7 /** 8 * 功能概要:缓冲线程池实例-execute运行 9 */ 10 class Handle implements Runnable { 11 private String name; 12 public Handle(String name) { 13 this.name = name; 14 } 15 @Override 16 public void run() { 17 System.out.println("线程" + name +" Start.Time = "+new Date()); 18 processCommand(); 19 System.out.println("线程" + name +" End.Time = "+new Date()); 20 } 21 private void processCommand() { 22 try { 23 Thread.sleep(1000); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 } 28 @Override 29 public String toString(){ 30 return this.name; 31 } 32 } 33 34 //验证CachedThreadPool 35 public class testCachedThreadPool{ 36 public static void main(String[] args) { 37 System.out.println("Main: Starting at: "+ new Date()); 38 ExecutorService exec = Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE 39 for(int i = 0; i < 10; i++) { 40 exec.execute(new Handle(String.valueOf(i))); 41 } 42 exec.shutdown(); //执行到此处并不会马上关闭线程池,但之后不能再往线程池中加线程,否则会报错 43 System.out.println("Main: Finished all threads at"+ new Date()); 44 } 45 }