Java 使用线程池执行多个任务的示例
在执行一系列带有io操作(例如下载文件),且互不相关的异步任务时,采用多线程可以很极大的提高运行效率。线程池包含了一系列的线程,并且可以管理这些线程。例如:创建线程,销毁线程等。本文将介绍如何使用java中的线程池执行任务。
1 任务类型
在使用线程池执行任务之前,我们弄清楚什么任务可以被线程池调用。按照任务是否有返回值可以将任务分为两种,分别是实现runnable的任务类(无参数无返回值)和实现callable接口的任务类(无参数有返回值)。在打代码时根据需求选择对应的任务类型。
1.1 实现runnable接口的类
多线程任务类型,首先自然想到的就是实现 runnable 接口的类,runnable接口提供了一个抽象方法run,这个方法无参数,无返回值。例如:
或者java 8 及以上版本更简单的写法:
1.2 实现callable接口的类
于runnable一样callable也只有一个抽象方法,不过该抽象方法有返回值。在实现该接口的时候需要制定返回值的类型。例如:
2 线程池类型
java.util.concurrent.executors 提供了一系列静态方法来创建各种线程池。下面例举出了主要的一些线程池及特性,其它未例举线程池的特性可由下面这些推导出来。
2.1 线程数固定的线程池 fixed thread pool
顾名思义,这种类型线程池线程数量是固定的。如果线程数量设置为n,则任何时刻该线程池最多只有n个线程处于运行状态。当线程池中处于饱和运行状态时,再往线程池中提交的任务会被放到执行队列中。如果线程池处于不饱和状态,线程池也会一直存在,直到executeservice 的shutdown方法被调用,线程池才会被清除。
2.2 可缓存的线程池 cached thread pool
这种类型的线程池初始大小为0个线程,随着往池里不断提交任务,如果线程池里面没有闲置线程(0个线程也表示没有闲置线程),则会创建新的线程,保证没有任务在等待;如果有闲置线程,则复用闲置状态线程执行任务。处于闲置状态的线程只会在线程池中缓存60秒,闲置时间达到60s的线程会被关闭并移出线程池。在处理大量短暂的(官方说法:short-lived)异步任务时可以显著得提供程序性能。
2.3 单线程池
这或许不能叫线程池了,由于它里面的线程永远只有1个,而且自始至终都只有1个(为什么说这句话,因为要和 executors.newfixedthreadpool(1) 区别开来),所以还是叫它“单线程池把”。你尽可以往单线程池中添加任务,但是每次只执行1个,且任务是按顺序执行的。如果前面的任务出现了异常,当前线程会被销毁,但1个新的线程会被创建用来执行后面的任务。以上这些和线程数只有1个的线程fixed thread pool一样。两者唯一不同的是, executors.newfixedthreadpool(1) 可以在运行时修改它里面的线程数,而 executors.newsinglethreadexecutor() 永远只能有1个线程。
2.4 工作窃取线程池
扒开源码,会发现工作窃取线程池本质是 forkjoinpool ,这类线程池充分利用cpu多核处理任务,适合处理消耗cpu资源多的任务。它的线程数不固定,维护的任务队列有多个,当一个任务队列完成时,相应的线程会从其它的任务队列中窃取任务执行,这也意味着任务的开始执行顺序并和提交顺序相同。如果有更高的需求,可以直接通过forkjoinpool获取线程池。
2.5 计划任务线程池
计划任务线程池可以按计划执行某些任务,例如:周期性的执行某项任务。
3 使用线程池执行任务
前面提到,任务类型分为有返回值和无返回值的类型,这里的调用也分为有返回值调用和无返回值的调用。
3.1 无返回值任务的调用
如果是无返回值任务的调用,可以用execute或者submit方法,这种情况下二者本质上一样。为了于有返回值任务调用保持统一,建议采用submit方法。
如果有一个任务集合,可以一个个提交。
3.2 有返回值任务的调用
有返回值的任务需要实现callable接口,实现的时候在泛型位置指定返回值类型。在调用submit方法时会返回一个future对象,通过future的方法get()可以拿到返回值。这里需要注意的是,调用get()时代码会阻塞,直到任务完成,有返回值。
如果要提交一批任务,executorservice除了可以逐个提交之外,还可以调用invokeall一次性提交,invokeall的内部实现其实就是用一个循环逐个提交任务。invokeall返回的值是一个future list。
invokeany方法也很有用,线程池执行若干个实现了callable的任务,然后返回最先执行结束的任务的值,其它未完成的任务将被正常取消掉不会有异常。如下代码不会输出“hello”
输出:
另外,在查看executorservice源码时发现它还提供了一个方法 <t> future<t> submit(runnable task, t result);
,可以通过这个方法提交一个实现了runnable接口的任务,然后有返回值,而runnable接口中的run方法时没有返回值的。那它的返回值是哪来的呢?其实问题在于该submit方法后面的一个参数,这个参数值就是返回的值。调用submit方法之后,有一通操作,然后直接把result参数返回了。
4 小结
在利用多线程处理任务时,应该根据情况选择合适的任务类型和线程池类型。如果无返回值,可以采用实现runnable或callable接口的任务;如果有返回值,应该使用实现callable接口的任务,返回值通过future的get方法取到。选用线程池时,如果只用1个线程,用单线程池或者容量为1的固定容量线程池;处理大量short-live任务是,使用可缓存的线程池;若要有计划或者循环执行某些任务,可以采用计划任务线程池;如果任务需要消耗大量的cpu资源,应用工作窃取线程池。
以上就是java 使用线程池执行多个任务的示例的详细内容,更多关于java 线程池执行任务的资料请关注其它相关文章!
下一篇: Geohash的原理、算法和具体应用探究