系统启动新线程的成本相对较高,因为它涉及到与操作系统的交互。在这种情况下,使用线程池可以提供良好的性能,特别是当需要创建大量的短生存线程时,应考虑使用线程池。
与数据库连接池类似,当系统启动时,线程池创建了大量的空闲线程。程序将Runnable对象传输到线程池,线程池将启动线程执行对象的Run方法。当Run方法完成时,线程不会死亡,而是返回到线程池,等待下一个Runnable对象的Run方法。
此外,使用线程池可以有效地控制系统中并发线程的数量,但当系统中包含大量并发线程时,系统性能会急剧下降,甚至JVM崩溃。线程池的最大线程参数可以控制系统中并发线程不超过这个数字。
在JDK1.5之前,开发者必须手动实现自己的线程池,JDK1.5之后,Java内部建立支持线程池。
所有与多线程并发的支持类别都在java.lang.在concurrent包中。我们可以使用内部类别来控制多线程的执行。
一、Executors类
JDK1.5.提供Executors工厂类生产连接池,其中包含以下静态工程方法创建连接池:
1、public static ExecutorService newFixedThreadPool(int nThreads):创建具有固定线程数的可重用线程池。
2、public static ExecutorService newSingleThreadExecutor():创建一个只有单线程的线程池,相当于newfixedthreadPol方法是1的参数
3、public static ExecutorService newCachedThreadPool():创建具有缓存功能的线程池,系统根据需要创建线程,这些线程将缓存在线程池中。
4、public static ScheduledExecutorService newSingleThreadScheduledExecutor:创建只有一个线程的线程池,他可以在指定的延迟后执行线程任务
5、public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,可在指定延迟后执行线程任务,corePolSize指池中保存的线程数,即使线程是免费的,它也被保存在线程池中。
上述方法都有一种重载方法,多引入ThreadFactory参数的重载方法,使用较少。
二、Executorservice
从以上五种方法可以看出,前三种方法的返回值都是ExecutorService对象。ExecutorService对象代表一个尽快执行线程的线程池(只要线程池中有空闲线程,就可以立即执行线程任务),程序只需要向线程池提交Runnable对象或Callable对象,线程就会尽快执行任务。
ExecutorService有几种重要的方法:
方法摘要
boolean
isShutdown()
若此执行程序已关闭,则返回true。
boolean
isTerminated()
如果所有任务在关闭后完成,则返回true。
void
shutdown()
启动顺序关闭,执行之前提交的任务,但不接受新任务。
List<Runnable>
shutdownNow()
试图停止所有正在执行的活动任务,暂停正在等待的任务,并返回等待执行的任务列表。
submit(Callable<T>task)
为执行提交返回值的任务,返回任务的未决结果 Future。
Future<?>
submit(Runnable
提交一个 Runnable 任务用于执行,并返回表示任务的任务 Future。
submit(Runnable
提交一个 Runnable 任务用于执行,并返回表示任务的任务 Future。
<T>Future<T>
更详细地参考JDK API文档。
Submit方法是正确的 executor接口execute方法更好的包装,建议使用submit方法。
三、ScheduleexecutorService类
在以上五种方法中,后两种方法的返回值为SchedulexecutorService对象。SchedulexecutorService代表可以在指定的延迟或周期性执行线程任务的线程池。
Scheduleexecutorservice是executorservice的子类。因此,还有直接提交任务的submit方法,并增加了一些延迟任务处理的方法:
方法摘要
schedule(Callable<V>callable, longdelay,TimeUnit
在给定延迟后启用创建和执行 ScheduledFuture。
ScheduledFuture<?>
schedule(Runnablecommand, longdelay,TimeUnit
在给定延迟后创建并执行一次性操作。
ScheduledFuture<?>
scheduleAtFixedRate(Runnablecommand, longinitialDelay, longperiod,TimeUnit
创建并执行给定初始延迟后首次启用的定期操作,后续操作有给定周期;即将在initialdelay之后执行,然后在initialdelay+period之后执行,然后在initialdelay之后执行 + 2 * period
ScheduledFuture<?>
scheduleWithFixedDelay(Runnablecommand, longinitialDelay, longdelay,TimeUnit
创建并执行给定初始延迟后首次启用的定期操作,然后在每次执行终止和下一次执行开始之间存在给定延迟。
<V>ScheduledFuture<V>
以下是线程池的简单使用:
1、固定尺寸的线程池:
1. package com.tao.test; 2. 3. import java.util.concurrent.ExecutorService; 4. import java.util.concurrent.Executors; 5. 6. public class PoolTest { 7. public static void main(String[] args) { 8. 5);///创建一个固定大小为5的线程池 9. for(int i=0;i<7;i++){ 10. new MyThread()); 11. } 12. pool.shutdown(); 13. } 14. } 15. class MyThread extends Thread{ 16. @Override 17. public void run() { 18. “正在执行。。。。"); 19. } 20. }
输出结果:
[java] view plain copy
1. pool-1-thread-1正在执行。。。 2. pool-1-thread-3正在执行。。。 3. pool-1-thread-2正在执行。。。 4. pool-1-thread-4正在执行。。。 5. pool-1-thread-4正在执行。。。 6. pool-1-thread-5正在执行。。。 7. pool-1-thread-1正在执行。。。
可以看出,虽然我们创建了7个MyThread线程对象,但由于受线程池大小的限制,只打开了5个线程,从而减少了并发线程的数量。
2、单任务线程池:
[java] view plain copy
输出结果:
1. public class PoolTest { 2. public static void main(String[] args) { 3. ///创建单线程池 4. for(int i=0;i<7;i++){ 5. new MyThread()); 6. } 7. pool.shutdown(); 8. } 9. }
[java] view plain copy
可以看出,线程池只打开一个线程。
1. pool-1-thread-1正在执行。。。 2. pool-1-thread-1正在执行。。。 3. pool-1-thread-1正在执行。。。 4. pool-1-thread-1正在执行。。。 5. pool-1-thread-1正在执行。。。 6. pool-1-thread-1正在执行。。。 7. pool-1-thread-1正在执行。。。
3、创建可变尺寸的线程池
[java] view plain copy
输出结果:
1. public class PoolTest { 2. public static void main(String[] args) { 3. ExecutorService pool=Executors.newCachedThreadPool(); 4. for(int i=0;i<5;i++){ 5. new MyThread()); 6. } 7. pool.shutdown(); 8. } 9. }
[java] view plain copy
1. pool-1-thread-正在执行中。。。。 2. pool-1-thread-正在执行中。。。。 3. pool-1-thread-正在执行中。。。。 4. pool-1-thread-正在执行中。。。。 5. pool-1-thread-正在执行中。。。。
可以看出,我们对线程池的大小没有限制,但它会根据需要创建线程。
4、延迟线程池
[java] view plain copy
1. public class PoolTest { 2. public static void main(String[] args) { 3. 6); 4. for(int i=0;i<4;i++){ 5. new MyThread()); 6. } 7. 8. new MyThread(), 1000, TimeUnit.MILLISECONDS); 9. new MyThread(), 1000, TimeUnit.MILLISECONDS); 10. pool.shutdown(); 11. } 12. }
输出结果:
[java] view plain copy
1. pool-1-thread-正在执行中。。。。 2. pool-1-thread-正在执行中。。。。 3. pool-1-thread-正在执行中。。。。 4. pool-1-thread-正在执行中。。。。 5. pool-1-thread-6正在执行中。。。。 6. pool-1-thread-正在执行中。。。。
很明显,最后两个线程并没有立即执行,而是延迟了一秒钟。
5、单任务延迟线程池
[java] view plain copy
1. public class PoolTest { 2. public static void main(String[] args) { 3. ScheduledExecutorService pool=Executors.newSingleThreadScheduledExecutor(); 4. for(int i=0;i<4;i++){ 5. new MyThread()); 6. } 7. 8. new MyThread(), 1000, TimeUnit.MILLISECONDS); 9. new MyThread(), 1000, TimeUnit.MILLISECONDS); 10. pool.shutdown(); 11. } 12. }
以上是JDK为我包装的线程池。我们也可以定义线程池并查看源代码。我们发现Excutors中获取线程的静态方法是调用ThreadPolExecutor的内部结构方法。例如:
[java] view plain copy
1. public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { 2. return new ThreadPoolExecutor(nThreads, nThreads, 3. 0L, TimeUnit.MILLISECONDS, 4. new LinkedBlockingQueue<Runnable>(), 5. threadFactory); 6. }
可以看出,它通过调用ThreadPolexecutor的结构方法返回线程池。因此,我们也可以手动调用ThreadPolexecutor的各种结构方法来定义我们的线程池规则,但一般来说,使用自己的线程池就足够了,不需要自己实现。