java线程池介绍及简单使用举例

xiaoxiao2021-02-28  58

多线程虽然能够提升程序的性能,但其实也是一把双刃剑。"为每一个任务分配一个线程"的问题在于资源管理的复杂性。当我们需要频繁的创建多个线程进行耗时操作时,每次通过new Thread来创建并不是一种好的办法。new Thread 新建和销毁对象的性能较差,线程缺乏统一的管理,而且可能出现无限制的创建线程。

每当看到这种形式的代码时: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开发进阶从小工到专家》)

     

转载请注明原文地址: https://www.6miu.com/read-83483.html

最新回复(0)