Java使⽤线程池执⾏多个任务的⽰例
这篇⽂章主要介绍了Java 使⽤线程池执⾏多个任务的⽰例,帮助⼤家更好的理解和学习使⽤Java,感兴趣的朋友可以了解下
在执⾏⼀系列带有IO操作(例如下载⽂件),且互不相关的异步任务时,采⽤多线程可以很极⼤的提⾼运⾏效率。线程池包含了⼀系列的线程,并且可以管理这些线程。例如:创建线程,销毁线程等。本⽂将介绍如何使⽤Java中的线程池执⾏任务。
1 任务类型
在使⽤线程池执⾏任务之前,我们弄清楚什么任务可以被线程池调⽤。按照任务是否有返回值可以将任务分为两种,分别是实现Runnable 的任务类(⽆参数⽆返回值)和实现Callable接⼝的任务类(⽆参数有返回值)。在打代码时根据需求选择对应的任务类型。
1.1 实现Runnable接⼝的类
多线程任务类型,⾸先⾃然想到的就是实现 Runnable 接⼝的类,Runnable接⼝提供了⼀个抽象⽅法run,这个⽅法⽆参数,⽆返回值。例如:
Runnable task =new Runnable(){
@Override
public void run(){
System.out.println("Execute task.");
}
};
或者Java 8 及以上版本更简单的写法
Runnable task =()->{
java线程池创建的四种System.out.println("Execute task.");
};
1.2 实现Callable接⼝的类
于Runnable⼀样Callable也只有⼀个抽象⽅法,不过该抽象⽅法有返回值。在实现该接⼝的时候需要制定返回值的类型。例如:
Callable<String> callableTask =()->"finished";
于Runnable⼀样Callable也只有⼀个抽象⽅法,不过该抽象⽅法有返回值。在实现该接⼝的时候需要制定返回值的类型。例如:
Callable<String> callableTask =()->"finished";
2 线程池类型
urrent.Executors 提供了⼀系列静态⽅法来创建各种线程池。下⾯例举出了主要的⼀些线程池及特性,其它未例举线程池的特性可由下⾯这些推导出来。
2.1 线程数固定的线程池 Fixed Thread Pool
顾名思义,这种类型线程池线程数量是固定的。如果线程数量设置为n,则任何时刻该线程池最多只有n个线程处于运⾏状态。当线程池中处于饱和运⾏状态时,再往线程池中提交的任务会被放到执⾏队列中。如果线程池处于不饱和状态,线程池也会⼀直存在,直到ExecuteService 的shutdown⽅法被调⽤,线程池才会被清除。
// 创建线程数量为5的线程池。
ExecutorService executorService = wFixedThreadPool(5);
2.2 可缓存的线程池 Cached Thread Pool
这种类型的线程池初始⼤⼩为0个线程,随着往池⾥不断提交任务,如果线程池⾥⾯没有闲置线程(0个线程也表⽰没有闲置线程),则会创建新的线程,保证没有任务在等待;如果有闲置线程,则复⽤闲置状态线程执⾏任务。处于闲置状态的线程只会在线程池中缓存60秒,闲置时间达到60s的线程会被关闭并移出线程池。在处理⼤量短暂的(官⽅说法:short-lived)异步任务时可以显著得提供程序性能。
//创建⼀个可缓存的线程池
ExecutorService executorService = wCachedThreadPool();
这或许不能叫线程池了,由于它⾥⾯的线程永远只有1个,⽽且⾃始⾄终都只有1个(为什么说这句话,因为要和
Pool⼀样。两者唯⼀不同的是, wFixedThreadPool(1) 可以在运⾏时修改它⾥⾯的线程数,⽽ wSingleThreadExecutor() 永远只能有1个线程。
//创建⼀个单线程池
ExecutorService executorService = wSingleThreadExecutor();
2.4 ⼯作窃取线程池
扒开源码,会发现⼯作窃取线程池本质是 ForkJoinPool ,这类线程池充分利⽤CPU多核处理任务,适合处理消耗CPU资源多的任务。它的线程数不固定,维护的任务队列有多个,当⼀个任务队列完成时,相应的线程会从其它的任务队列中窃取任务执⾏,这也意味着任务的开始执⾏顺序并和提交顺序相同。如果有更⾼的需求,可以直接通过ForkJoinPool获取线程池。
//创建⼀个⼯作窃取线程池,使⽤CPU核数等于机器的CPU核数
ExecutorService executorService = wWorkStealingPool();
//创建⼀个⼯作窃取线程池,使⽤CPU 3 个核进⾏计算,⼯作窃取线程池不能设置线程数
ExecutorService executorService2 = wWorkStealingPool(3);
2.5 计划任务线程池
计划任务线程池可以按计划执⾏某些任务,例如:周期性的执⾏某项任务。
// 获取⼀个⼤⼩为2的计划任务线程池
ScheduledExecutorService scheduledExecutorService = wScheduledThreadPool(2);
// 添加⼀个打印当前线程信息计划任务,该任务在3秒后执⾏
scheduledExecutorService.schedule(()->{ System.out.println(Thread.currentThread());},3, TimeUnit.SECONDS);
// 添加⼀个打印当前线程信息计划任务,该任务在2秒后⾸次执⾏,之后每5秒执⾏⼀次。如果任务执⾏时间超过了5秒,则下⼀次将会在前⼀次执⾏完成之后⽴即执⾏
scheduledExecutorService.scheduleAtFixedRate(()->{ System.out.println(Thread.currentThread());},2,5, TimeUnit.SECONDS);
// 添加⼀个打印当前线程信息计划任务,该任务在2秒后⾸次执⾏,之后每次在任务执⾏之后5秒执⾏下⼀次。
scheduledExecutorService.scheduleWithFixedDelay(()->{ System.out.println(Thread.currentThread());},2,5, TimeUnit.SECONDS);
// 逐个清除 idle 状态的线程
scheduledExecutorService.shutdown();
// 阻塞,在线程池被关调之前代码不再往下⾛
scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
3 使⽤线程池执⾏任务
前⾯提到,任务类型分为有返回值和⽆返回值的类型,这⾥的调⽤也分为有返回值调⽤和⽆返回值的调⽤。
3.1 ⽆返回值任务的调⽤
如果是⽆返回值任务的调⽤,可以⽤execute或者submit⽅法,这种情况下⼆者本质上⼀样。为了于有返回值任务调⽤保持统⼀,建议采⽤submit⽅法。
/
/创建⼀个线程池
ExecutorService executorService = wFixedThreadPool(3);
//提交⼀个⽆返回值的任务(实现了Runnable接⼝)
executorService.submit(()->System.out.println("Hello"));
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
如果有⼀个任务集合,可以⼀个个提交。
ExecutorService executorService = wFixedThreadPool(3);
List<Runnable> tasks = Arrays.asList(
()->System.out.println("Hello"),
()->System.out.println("World"));
/
/逐个提交任务
tasks.forEach(executorService::submit);
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
3.2 有返回值任务的调⽤
有返回值的任务需要实现Callable接⼝,实现的时候在泛型位置指定返回值类型。在调⽤submit⽅法时会返回⼀个Future对象,通过Future的⽅法get()可以拿到返回值。这⾥需要注意的是,调⽤get()时代码会阻塞,直到任务完成,有返回值。
ExecutorService executorService = wFixedThreadPool(2);
Future<String> future = executorService.submit(()->"Hello");
System.out.println(future.isDone());//false
String value = ();
System.out.println(future.isDone());//true
System.out.println(value);//Hello
如果要提交⼀批任务,ExecutorService除了可以逐个提交之外,还可以调⽤invokeAll⼀次性提交,invokeAll的内部实现其实就是⽤⼀个循环逐个提交任务。invokeAll返回的值是⼀个Future List。
ExecutorService executorService = wFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(()->"Hello",()->"World");
List<Future<String>> futures = executorService.invokeAll(tasks);
invokeAny⽅法也很有⽤,线程池执⾏若⼲个实现了Callable的任务,然后返回最先执⾏结束的任务的值,其它未完成的任务将被正常取消掉不会有异常。如下代码不会输出“Hello”
ExecutorService executorService = wFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(
()->{
Thread.sleep(500L);
System.out.println("Hello");
return"Hello";
},()->{
System.out.println("World");
return"World";
});
String s = executorService.invokeAny(tasks);
System.out.println(s);//World
输出:
World
World
另外,在查看ExecutorService源码时发现它还提供了⼀个⽅法 Future submit(Runnable task, T result); ,可以通过这个⽅法提交⼀个实现了Runnable接⼝的任务,然后有返回值,⽽Runnable接⼝中的run⽅法时没有返回值的。那它的返回值是哪来的呢?其实问题在于该submit⽅法后⾯的⼀个参数,这个参数值就是返回的值。调⽤submit⽅法之后,有⼀通操作,然后直接把result参数返回了。
ExecutorService executorService = wFixedThreadPool(1);
Future<String> future = executorService.submit(()-> System.out.println("Hello"),"World");
System.out.());//输出:World
在利⽤多线程处理任务时,应该根据情况选择合适的任务类型和线程池类型。如果⽆返回值,可以采⽤实现Runnable或Callable接⼝的任务;如果有返回值,应该使⽤实现Callable接⼝的任务,返回值通过Future的get⽅法取到。选⽤线程池时,如果只⽤1个线程,⽤单线程池或者容量为1的固定容量线程池;处理⼤量short-live任务是,使⽤可缓存的线程池;若要有计划或者循环执⾏某些任务,可以采⽤计划任务线程池;如果任务需要消耗⼤量的CPU资源,应⽤⼯作窃取线程池。
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论