Java线程池的使用
频繁的创建和销毁线程需要花费大量时间,Java支持创建线程池,将多任务交给一组线程执行,提高执行效率。
1、创建线程池
Java标准库提供 ExecutorService 接口表示线程池,实现类有FixedThreadPool、CachedThreadPool、SingleThreadExecutor;创建线程池的方法被封装到Executors类。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(6); for (int i = 0; i < 10; i++) { es.submit(new Task(i)); } es.shutdown(); } } class Task implements Runnable { private int count = 0; Task(int count) { this.count = count; } @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.count); } } // 2 // 4 // 1 // 5 // 3 // 0 // 8 // 6 // 9 // 7
由于FixedThreadPool 总是创建固定的线程数量,当一次性提交的任务数量超过线程池中线程的数时,多出的任务需要等待。总结:
- FixedThreadPool:创建指定数量线程的线程池;
- CachedThreadPool:根据任务动态调整线程数的线程池,同时支持创建线程动态范围的线程池;
- SingleThreadExecutor:单线程执行的线程池。
线程池结束方式:
- es.shutdown() :等待任务完成后,关闭线程池;
- es.shutdownNow(): 立即结束任务,并关闭线程池;
- es.awaitTermination(): 指定等待时间后,关闭线程池。
2、ScheduledThreadPool
ScheduledThreadPool 重复执行任务的线程池,Java标准库使用 ScheduledExecutorService 表示接口,创建方法同样被封装到Executors中。
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ThreadPool { public static void main(String[] args) throws InterruptedException { ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
ses.schedule(new ScheduledTask(), 1, TimeUnit.SECONDS); ses.scheduleAtFixedRate(new ScheduledTask(), 0, 1, TimeUnit.SECONDS); //ses.awaitTermination(10, TimeUnit.SECONDS); Thread.sleep(10000); ses.shutdownNow(); } } class ScheduledTask implements Runnable { private int i = 0; @Override public void run() { System.out.println(i++); } }
其中:
- ses.schedule(new ScheduledTask(), 1, TimeUnit.SECONDS): 表示1秒后执行任务;
- ses.scheduleAtFixedRate(new ScheduledTask(), 0, 1, TimeUnit.SECONDS):表示0秒后,每过 1 秒执行一次;
- ses.scheduleWithFixedDelay(new ScheduledTask(), 0, 1, TimeUnit.SECONDS):表示0秒后,每间隔 1 秒执行一次;
3、Future对象
使用线程池可以方便的处理多任务,只需要任务实现Runable接口,调用submit方法提交执行即可。但缺点是无法返回值,Java提供Future对象返回执行结果,但异步任务需要实现Callable接口。
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class ThreadPool { public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { ExecutorService executor = Executors.newFixedThreadPool(4); Callable<String> task = new Task(); Future<String> future = executor.submit(task); String res = future.get(1, TimeUnit.SECONDS); System.out.println(res); } }
class Task implements Callable<String>{ public String call() throws InterruptedException { Thread.sleep(2000); //模拟耗时任务 return "done"; } }
可以看到 Callable接口是泛型接口,可以指定返回类型。需要注意 future对象:
- future.get()直接调用可能会阻塞当前线程;
- future.get(long time, TimeUnit unit)可指定等待时间,超时会报timeoutException;
- future.cancel()取消任务;
- future.isDone(),判断任务是否完成。
4、CompletableFuture
使用Future获取异步执行的结果或阻塞当前线程,Java 8引入的CompletableFuture,支持异步回调,其用法类似JavaScript中的Promise对象的用法。
import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; public class ThreadPool { public static void main(String[] args) throws InterruptedException { // 异步执行 CompletableFuture<String> apple = CompletableFuture.supplyAsync(new FetchApple()); apple.thenAccept((result) -> { System.out.println(result); }); apple.exceptionally((e) -> { e.printStackTrace(); return null; }); Thread.sleep(3000); } } class FetchApple implements Supplier<String> { @Override public String get() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "Apple"; } }
CompletableFuture支持传入成功和失败的回调,在将来的时刻执行,并且不会阻塞当前线程。同时 CompletableFuture支持类似于Promise的链式回调,Promise.race的竞争并发执行和类似Promise.all的全部执行,但是用法上会复杂许多。CompletableFuture会使用默认线程池。
由于Java的严谨性,CompletableFuture.supplyAsync传入的对象需要实现Supplier<T>接口、成功的回调thenAccept传入对象需要实现Consumer<T>接口、失败的回调exceptionally需要传入实现Function<T,R>接口的对象。但好在支持的lambda语法可以进行简化。
三者对比
对象实现接口 | 覆写方法 @Override | 问题 | |
线程池 | Runnable | run | 无法返回值 |
Future<V> | Callable<V> | call | get方法阻塞 |
CompletableFuture<V> | Supplier<T> | get | 异步接收对象需要实现Consumer<T>接口,异常需要实现Function<T, R> 接口。 |
参考链接:
https://www.liaoxuefeng.com/wiki/1252599548343744/1306581155184674