现在我们来解决上一章在存在的问题1;
在Java中我们可以使用线程池来实现线程复用,每当我们需要执行异步任务时,可以把任务投递到线程池里进行异步执行。我们可以修改上节的代码,使用线程池来执行异步任务,修改后代码如下:
public static void doSomethingA() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--- doSomethingA---");
}
public static void doSomethingB() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--- doSomethingB---");
}
// 0自定义线程池
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS, AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
// 1.开启异步单元执行任务A
POOL_EXECUTOR.execute(() -> {
try {
doSomethingA();
} catch (Exception e) {
e.printStackTrace();
}
});
// 2.执行任务B
doSomethingB();
// 3.同步等待线程A运行结束
System.out.println(System.currentTimeMillis() - start);
// 4.挂起当前线程
Thread.currentThread().join();
}
上面代码0创建了一个线程池,这里我们设置线程池核心线程个数为当前物理机的CPU核数,最大线程个数为当前物理机CPU核数的2倍;设置线程池阻塞队列的大小为5;需要注意的是,我们将线程池的拒绝策略设置为CallerRunsPolicy
,即当线程池任务饱和,执行拒绝策略时不会丢弃新的任务,而是会使用调用线程来执行;另外我们使用了命名的线程创建工厂,以便排查问题时可以方便追溯是哪个相关业务。创建完线程池后,代码1则把异步任务提交到了线程池内运行,而不是直接开启一个新线程来运行;这里使用线程池起到了复用线程的作用,避免了线程的频繁创建与销毁,另外对线程个数也起到了限制作用。
方式二
其实通过上面代码我们可以进一步释放main线程的负担,也就是可以把任务doSomethingB
的执行也提交到线程池内进行异步执行,代码如下:
// 0自定义线程池
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS, AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
// 1.开启异步单元执行任务A
POOL_EXECUTOR.execute(() -> {
try {
doSomethingA();
} catch (Exception e) {
e.printStackTrace();
}
});
// 2.执行任务B
POOL_EXECUTOR.execute(() -> {
try {
doSomethingB();
} catch (Exception e) {
e.printStackTrace();
}
});
// 3.同步等待线程A运行结束
System.out.println(System.currentTimeMillis() - start);
// 4.挂起当前线程
Thread.currentThread().join();
}
如上面代码所示,main函数所在线程只需要把两个任务提交到线程池后就可以做自己的事情了,具体两个任务是由线程池中的线程执行。
上面演示了向线程池内投递异步任务并没有返回值的情况,其实我们可以向线程池投递一个Callable
类型的异步任务,并且获取其执行结果,代码如下:
public static String doSomethingA() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--- doSomethingA---");
return "A Task Done";
}
// 0自定义线程池
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS, AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(5), new NamedThreadFactory("ASYNC-POOL"), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 1.开启异步单元执行任务A
Future<?> resultA = POOL_EXECUTOR.submit(() -> doSomethingA());
// 2.同步等待执行结果
System.out.println(resultA.get());
}
如上面代码所示,doSomethingA
方法具有String类型的返回值,代码0创建了一个线程池,在main
方法中,代码1使用lambda表达式将Callable
类型的任务提交到线程池,提交后会马上返回一个Future
对象,代码2在futureA
上调用get()方法阻塞等待异步任务的执行结果。
如上代码确实可以在main函数所在线程获取到异步任务的执行结果,但是main线程必须以阻塞的代价来获取结果,在异步任务执行完毕前,main函数所在线程就不能做其他事情了,这显然不是我们所需要的,具体怎么解决这个问题,下章揭晓。