当前位置: 首页 > 图灵资讯 > 技术篇> Java多线程(四)、线程池

Java多线程(四)、线程池

来源:图灵教育
时间:2023-04-28 09:24:58

系统启动新线程的成本相对较高,因为它涉及到与操作系统的交互。在这种情况下,使用线程池可以提供良好的性能,特别是当需要创建大量的短生存线程时,应考虑使用线程池。

与数据库连接池类似,当系统启动时,线程池创建了大量的空闲线程。程序将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的各种结构方法来定义我们的线程池规则,但一般来说,使用自己的线程池就足够了,不需要自己实现。