java线程池介绍及简单使用举例
2017-05-07 21:14
701 查看
多线程虽然能够提升程序的性能,但其实也是一把双刃剑。"为每一个任务分配一个线程"的问题在于资源管理的复杂性。当我们需要频繁的创建多个线程进行耗时操作时,每次通过new Thread来创建并不是一种好的办法。new Thread 新建和销毁对象的性能较差,线程缺乏统一的管理,而且可能出现无限制的创建线程。
每当看到这种形式的代码时:new Thread(runnable).start()
并且你希望获得一种更灵活的执行策略,请考虑使用Executor来替代Thread。(参考《Java并发编程实战》)
因此,我们可以通过使用线程池来管理线程。线程池是指管理一组同构工作线程的资源池。其原理简单解释就是会创建多个线程并进行管理,提交给线程的任务会被线程池指派给其中的线程进行执行。
java.util.concurrent提供了一种灵活的线程池实现作为Executor框架的一部分,Executor接口如下:
线程池都实现了ExecutorService接口,该接口定义了线程池需要实现的方法:
ThreadPoolExecutor继承自抽象类AbstractExecutorService,该抽象类实现了ExecutorService接口。ThreadPoolExecutor也是我们运用最多的线程池。
ScheduledThreadPoolExcecutor 继承了ThreadPoolExecutor 并实现了ScheduledExecutorService接口,ScheduledExecutorService接口继承自ExecutorService接口。ScheduledThreadPoolExcecutor用于周期性的执行任务,和Timer类类似。但Timer存在一些缺陷,因此应该考虑使用ScheduledThreadPoolExcecutor来代替它(Timer支持基于绝对时间而不是相对时间的调度机制,因此任务的执行对系统时钟变化很敏感,而ScheduledThreadPoolExcecutor只支持基于相对时间的调度)。
我们可以通过ThreadPoolExecutor的构造函数来实例化一个对象,但由于创建参数相对复杂,通常选择Exectors工厂类的静态方法来创建一个线程池:
newFixedThreadPool。将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程池,直到达到线程池的最大数量,这时线程池的规模将不再变化。
newCachedThreadPool。将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。
newSingleThreadPool。是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadPool能确保依照任务在队列中的顺序来串行执行(例如FIFO、LIFO、优先级)。
newScheduledThreadPool。创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
newFixedThreadPool和newCachedThreadPool 返回通用的ThreadPoolExecutor实例,这些实例可以直接用来构造专门用途的executor。
当默认的创建线程池策略无法满足要求时,那么可以通过ThreadPoolExecutor构造函数来实例化一个对象,根据自己的需求来实现定制,ThreadPoolExecutor最常见的构造函数形式如下:
corePoolSize:线程池中所保存的核心线程数。线程池启动后默认是空的,只有任务来临时才会创建线程以处理请求。
maximumPoolSize:线程池允许创建的最大线程数。它与corePoolSize的作用是调整“线程池中实际运行的线程的数量”。当新任务提交给线程池时,如果线程池中运行的线程数量小于corePoolSize,则创建新线程来执行任务;如果此时,线程池中运行的线程数量大于corePoolSize,但小于maximumPoolSize,则仅当阻塞队列满时才创建新线程。如果corePoolSize与maximumPoolSize相同,则创建固定大小的线程池。如果将maximumPoolSize设置为基本的无界值(如Integer.MAX_VALUE),则允许线程池适应任意数量的并发任务。
keepAliveTime:当前线程池线程总数达到核心线程数时,终止多余的空闲线程的时间。
Unit:keepAliveTime参数的时间单位,可选值有毫秒、秒、分等。
workQueue:任务队列。如果当前线程池达到核心线程数量corePoolSize后,且当前所有线程都处于活动状态时,则将新加入的任务放到此队列中。
threadFactory:线程工厂,让用户可以定制线程的创建过程,一般不需要设置。
Handler:拒绝策略,当线程池和任务队列workQueue都满了的情况下,对新加的任务采取的处理策略。
下面通过两个简单的例子来说明线程池的简单使用:
1.通过Executors.newFixedThreadPool(int)来创建一个固定数量的线程池,代码如下:
执行线程pool-1-thread-1
第0次计算,结果为6765
执行线程pool-1-thread-2
第1次计算,结果为6765
执行线程pool-1-thread-3
第2次计算,结果为6765
执行线程pool-1-thread-4
第3次计算,结果为6765
执行线程pool-1-thread-1
第4次计算,结果为6765
执行线程pool-1-thread-2
第5次计算,结果为6765
执行线程pool-1-thread-3
第6次计算,结果为6765
执行线程pool-1-thread-4
第7次计算,结果为6765
执行线程pool-1-thread-1
第8次计算,结果为6765
执行线程pool-1-thread-2
第9次计算,结果为6765
newCachedThreadPool
2.通过Executors.newCacheThreadPool()创建带缓存的线程池
有时,我们需要任务尽可能快的被执行,这需要线程池中的线程足够多,也就是说需要用空间来换时间,创建的线程越多,占用的内存消耗也越大。但由于其并发量也越大,因此执行的速度也越快。考虑这样一种场景,在新提交了一个任务后,由于当前没有空闲线程可执行,因此需要马上创建一个线程来执行该任务。这种场景可通过该方法来实现。代码如下:
代码中,通过newFixedThreadPool创建了一个线程池,并每次提交一个任务,来计算前30个的斐波那契数列,运行结果如下:
执行线程为:pool-1-thread-2,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-3,结果为:832040
执行线程为:pool-1-thread-4,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-5,结果为:832040
执行线程为:pool-1-thread-2,结果为:832040
执行线程为:pool-1-thread-6,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-5,结果为:832040
为了保证执行效率,每次提交任务,线程池都会创建一个新线程来执行任务,但前提是此时没有空闲线程才创建新线程,但有空闲线程时,则会使用空闲的线程来执行任务,如结果中所示,执行前4个任务时,线程池为每个任务都创建了一个线程,当执行到第5个任务时,此时第一个线程已经执行完任务,并处于空闲状态,那么第5个任务就被执行在第1个线程中了。
(参考书籍《Java并发编程实战》、《Android开发进阶从小工到专家》)
每当看到这种形式的代码时:new Thread(runnable).start()
并且你希望获得一种更灵活的执行策略,请考虑使用Executor来替代Thread。(参考《Java并发编程实战》)
因此,我们可以通过使用线程池来管理线程。线程池是指管理一组同构工作线程的资源池。其原理简单解释就是会创建多个线程并进行管理,提交给线程的任务会被线程池指派给其中的线程进行执行。
java.util.concurrent提供了一种灵活的线程池实现作为Executor框架的一部分,Executor接口如下:
public interface Executor { void execute(Runnable command); }虽然Executor是个简单的接口,但它缺位灵活且强大的异步任务执行框架提供了基础。
线程池都实现了ExecutorService接口,该接口定义了线程池需要实现的方法:
public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, Ti meUnit unit) throws InterruptedException; <T> Future<T> submit(Callable<T> task); //.......(代码省略其他用于任务提交的便利方法) }ExecutorService接口的实现有,ThreadPoolExecutor和ScheduledThreadPoolExcecutor。
ThreadPoolExecutor继承自抽象类AbstractExecutorService,该抽象类实现了ExecutorService接口。ThreadPoolExecutor也是我们运用最多的线程池。
ScheduledThreadPoolExcecutor 继承了ThreadPoolExecutor 并实现了ScheduledExecutorService接口,ScheduledExecutorService接口继承自ExecutorService接口。ScheduledThreadPoolExcecutor用于周期性的执行任务,和Timer类类似。但Timer存在一些缺陷,因此应该考虑使用ScheduledThreadPoolExcecutor来代替它(Timer支持基于绝对时间而不是相对时间的调度机制,因此任务的执行对系统时钟变化很敏感,而ScheduledThreadPoolExcecutor只支持基于相对时间的调度)。
我们可以通过ThreadPoolExecutor的构造函数来实例化一个对象,但由于创建参数相对复杂,通常选择Exectors工厂类的静态方法来创建一个线程池:
newFixedThreadPool。将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程池,直到达到线程池的最大数量,这时线程池的规模将不再变化。
newCachedThreadPool。将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。
newSingleThreadPool。是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadPool能确保依照任务在队列中的顺序来串行执行(例如FIFO、LIFO、优先级)。
newScheduledThreadPool。创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
newFixedThreadPool和newCachedThreadPool 返回通用的ThreadPoolExecutor实例,这些实例可以直接用来构造专门用途的executor。
当默认的创建线程池策略无法满足要求时,那么可以通过ThreadPoolExecutor构造函数来实例化一个对象,根据自己的需求来实现定制,ThreadPoolExecutor最常见的构造函数形式如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)参数说明:
corePoolSize:线程池中所保存的核心线程数。线程池启动后默认是空的,只有任务来临时才会创建线程以处理请求。
maximumPoolSize:线程池允许创建的最大线程数。它与corePoolSize的作用是调整“线程池中实际运行的线程的数量”。当新任务提交给线程池时,如果线程池中运行的线程数量小于corePoolSize,则创建新线程来执行任务;如果此时,线程池中运行的线程数量大于corePoolSize,但小于maximumPoolSize,则仅当阻塞队列满时才创建新线程。如果corePoolSize与maximumPoolSize相同,则创建固定大小的线程池。如果将maximumPoolSize设置为基本的无界值(如Integer.MAX_VALUE),则允许线程池适应任意数量的并发任务。
keepAliveTime:当前线程池线程总数达到核心线程数时,终止多余的空闲线程的时间。
Unit:keepAliveTime参数的时间单位,可选值有毫秒、秒、分等。
workQueue:任务队列。如果当前线程池达到核心线程数量corePoolSize后,且当前所有线程都处于活动状态时,则将新加入的任务放到此队列中。
threadFactory:线程工厂,让用户可以定制线程的创建过程,一般不需要设置。
Handler:拒绝策略,当线程池和任务队列workQueue都满了的情况下,对新加的任务采取的处理策略。
下面通过两个简单的例子来说明线程池的简单使用:
1.通过Executors.newFixedThreadPool(int)来创建一个固定数量的线程池,代码如下:
public class MyExecutorDemo { //执行的任务数量 private static int MAX = 10; public static void main(String args[]){ try { fixedThreadPool(4); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } private static void fixedThreadPool (int coreSize) throws InterruptedException,ExecutionException { //创建线程池 ExecutorService exec = Executors.newFixedThreadPool(coreSize); for(int i = 0; i < MAX; i++){ //提交任务 Future<Integer> task = exec.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("执行线程" + Thread.currentThread().getName()); return fibc(20); } }); //获取执行结果 System.out.println("第"+i+"次计算,结果为"+task.get()); } } //模拟耗时操作,定义一个斐波那契数列 private static int fibc(int num){ if (num == 0){ return 0; } if (num == 1){ return 1; } return fibc(num-1)+fibc(num-2); } }在上述代码中,通过fixedThreadPool启动了含有4个线程的线程池,我们看一下该方法的构造函数,
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }第一个和第二个参数时相同的值,则创建固定大小的线程池,最后的参数为无界队列,因此该线程池可容纳无限个任务。创建成功后,向线程池中通过submit提交了10个Callable任务,每个任务计算前20个的斐波那契数列。通过结果可以看出,线程池中有4个线程交替的执行任务,执行运算结果如下:
执行线程pool-1-thread-1
第0次计算,结果为6765
执行线程pool-1-thread-2
第1次计算,结果为6765
执行线程pool-1-thread-3
第2次计算,结果为6765
执行线程pool-1-thread-4
第3次计算,结果为6765
执行线程pool-1-thread-1
第4次计算,结果为6765
执行线程pool-1-thread-2
第5次计算,结果为6765
执行线程pool-1-thread-3
第6次计算,结果为6765
执行线程pool-1-thread-4
第7次计算,结果为6765
执行线程pool-1-thread-1
第8次计算,结果为6765
执行线程pool-1-thread-2
第9次计算,结果为6765
newCachedThreadPool
2.通过Executors.newCacheThreadPool()创建带缓存的线程池
有时,我们需要任务尽可能快的被执行,这需要线程池中的线程足够多,也就是说需要用空间来换时间,创建的线程越多,占用的内存消耗也越大。但由于其并发量也越大,因此执行的速度也越快。考虑这样一种场景,在新提交了一个任务后,由于当前没有空闲线程可执行,因此需要马上创建一个线程来执行该任务。这种场景可通过该方法来实现。代码如下:
private static void newCachedThreadPool () throws ExecutionException, InterruptedException{ ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < MAX; i++){ executorService.submit(new Runnable() { @Override public void run() { System.out.println("执行线程为:"+Thread.currentThread().getName()+ ",结果为:"+fibc(30)); } }); } }
代码中,通过newFixedThreadPool创建了一个线程池,并每次提交一个任务,来计算前30个的斐波那契数列,运行结果如下:
执行线程为:pool-1-thread-2,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-3,结果为:832040
执行线程为:pool-1-thread-4,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-5,结果为:832040
执行线程为:pool-1-thread-2,结果为:832040
执行线程为:pool-1-thread-6,结果为:832040
执行线程为:pool-1-thread-1,结果为:832040
执行线程为:pool-1-thread-5,结果为:832040
为了保证执行效率,每次提交任务,线程池都会创建一个新线程来执行任务,但前提是此时没有空闲线程才创建新线程,但有空闲线程时,则会使用空闲的线程来执行任务,如结果中所示,执行前4个任务时,线程池为每个任务都创建了一个线程,当执行到第5个任务时,此时第一个线程已经执行完任务,并处于空闲状态,那么第5个任务就被执行在第1个线程中了。
(参考书籍《Java并发编程实战》、《Android开发进阶从小工到专家》)
相关文章推荐
- java线程池的简单介绍与使用
- Java的开源项目:简单介绍Log4J的使用
- java 线程池的简单使用
- JAVA中线程池的简单使用
- Java中ArrayList的使用方法简单介绍
- 在Java的Hibernate框架中使用SQL语句的简单介绍
- 线程池(简单介绍及使用示例)
- java 之Math库类的使用简单介绍
- 使用Java实现简单的server/client回显功能的方法介绍
- Java(Android)线程池,介绍new Thread的弊端及Java四种线程池的使用
- Java中List的使用方法简单介绍
- JDK1.5中线程池的简单使用(java.util.concurrent.ThreadPoolExecut )
- 使用Java实现简单的server/client回显功能的方法介绍
- Android(java)的线程池:ExecutorService和Executors简单介绍
- [转]Java的开源项目:简单介绍Log4J的使用
- JDK5 线程池(java.util.concurrent.ThreadPoolExecutor) 使用介绍
- java 枚举类的简单介绍及使用枚举类的内部类编写的星期几的小案例
- Android(java)的线程池:ExecutorService和Executors简单介绍
- 介绍new Thread的弊端及Java四种线程池的使用
- java线程池的简单使用