前言
本篇文章将对线程池ThreadPoolExecutor
的使用进行说明。
正文
一. 执行无返回值任务
通过ThreadPoolExecutor
的execute()
方法,能执行Runnable
任务,示例如下。
public class ThreadPoolExecutorTest {
@Test
public void ThreadPoolExecutor执行简单无返回值任务() throws Exception {
// 创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
// 创建两个任务
Runnable firstRunnable = new Runnable() {
@Override
public void run() {
System.out.println("第一个任务执行");
}
};
Runnable secondRunnable = new Runnable() {
@Override
public void run() {
System.out.println("第二个任务执行");
}
};
// 让线程池执行任务
threadPoolExecutor.execute(firstRunnable);
threadPoolExecutor.execute(secondRunnable);
// 让主线程睡眠1秒,等待线程池中的任务被执行完毕
Thread.sleep(1000);
}
}
运行测试程序,结果如下。
二. 执行有返回值任务
通过ThreadPoolExecutor
的submit()
方法,能够执行Callable
任务,通过submit()
方法返回的RunnableFuture
能够拿到异步执行的结果。示例如下。
public class ThreadPoolExecutorTest {
@Test
public void ThreadPoolExecutor执行简单有返回值任务() throws Exception {
// 创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
// 创建两个任务,任务执行完有返回值
Callable<String> firstCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return "第一个任务返回值";
}
};
Callable<String> secondCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return "第二个任务返回值";
}
};
// 让线程池执行任务
Future<String> firstFuture = threadPoolExecutor.submit(firstCallable);
Future<String> secondFuture = threadPoolExecutor.submit(secondCallable);
// 获取执行结果,拿不到结果会阻塞在get()方法上
System.out.println(firstFuture.get());
System.out.println(secondFuture.get());
}
}
运行测试程序,结果如下。
三. 执行有返回值任务时抛出错误
如果ThreadPoolExecutor
在执行Callable
任务时,在Callable
任务中抛出了异常并且没有捕获,那么这个异常是可以通过Future
的get()
方法感知到的。示例如下。
public class ThreadPoolExecutorTest {
@Test
public void ThreadPoolExecutor执行简单有返回值任务时抛出错误() {
// 创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
// 创建一个任务,任务有返回值,但是执行过程中抛出异常
Callable<String> exceptionCallable = new Callable<String>() {
@Override
public String call() throws Exception {
throw new RuntimeException("发生了异常");
}
};
// 让线程池执行任务
Future<String> exceptionFuture = threadPoolExecutor.submit(exceptionCallable);
try {
System.out.println(exceptionFuture.get());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
运行测试程序,结果如下。
四. ThreadPoolExecutor通过submit方式执行Runnable
ThreadPoolExecutor
可以通过submit()
方法来运行Runnable
任务,并且还可以异步获取执行结果。示例如下。
public class ThreadPoolExecutorTest {
@Test
public void ThreadPoolExecutor通过submit的方式来提交并执行Runnable() throws Exception {
// 创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
// 创建结果对象
MyResult myResult = new MyResult();
// 创建Runnable对象
Runnable runnable = new Runnable() {
@Override
public void run() {
myResult.setResult("任务执行了");
}
};
// 通过ThreadPoolExecutor的submit()方法提交Runnable
Future<MyResult> resultFuture = threadPoolExecutor.submit(runnable, myResult);
// 获取执行结果
MyResult finalResult = resultFuture.get();
// myResult和finalResult的地址实际相同
Assert.assertEquals(myResult, finalResult);
// 打印执行结果
System.out.println(resultFuture.get().getResult());
}
private static class MyResult {
String result;
public MyResult() {}
public MyResult(String result) {
this.result = result;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
}
运行测试程序,结果如下。
实际上ThreadPoolExecutor
的submit()
方法无论是提交Runnable
任务还是Callable
任务,都是将任务封装成了RunnableFuture
接口的子类FutureTask
,然后调用ThreadPoolExecutor
的execute()
方法来执行FutureTask
。
五. 关闭线程池
关闭线程池可以通过ThreadPoolExecutor
的shutdown()
方法,但是shutdown()
方法不会去中断正在执行任务的线程,所以如果线程池里有Worker
正在执行一个永远不会结束的任务,那么shutdown()
方法是无法关闭线程池的。示例如下。
public class ThreadPoolExecutorTest {
@Test
public void 通过shutdown关闭线程池() {
// 创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
// 创建Runnable对象
Runnable runnable = new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
LockSupport.parkNanos(1000 * 1000 * 1000);
}
System.out.println(Thread.currentThread().getName() + " 被中断");
}
};
// 让线程池执行任务
threadPoolExecutor.execute(runnable);
threadPoolExecutor.execute(runnable);
// 调用shutdown方法关闭线程池
threadPoolExecutor.shutdown();
// 等待3秒观察现象
LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
}
}
运行测试程序,会发现在主线程中等待3秒后,也没有得到预期的打印结果。如果上述测试程序中使用shutdownNow
,则是可以得到预期打印结果的,示例如下。
public class ThreadPoolExecutorTest {
@Test
public void 通过shutdownNow关闭线程池() {
// 创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(300));
// 创建Runnable对象
Runnable runnable = new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
LockSupport.parkNanos(1000 * 1000 * 1000);
}
System.out.println(Thread.currentThread().getName() + " 被中断");
}
};
// 让线程池执行任务
threadPoolExecutor.execute(runnable);
threadPoolExecutor.execute(runnable);
// 调用shutdown方法关闭线程池
threadPoolExecutor.shutdownNow();
// 等待3秒观察现象
LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
}
}
运行测试程序,打印如下。
因为测试程序中的任务是响应中断的,而ThreadPoolExecutor
的shutdownNow()
方法会中断所有Worker
,所以执行shutdownNow()
方法后,正在运行的任务会响应中断并结束运行,最终线程池关闭。
假如线程池中运行着一个永远不会结束的任务,且这个任务不响应中断,那么无论是shutdown()
方法还是shutdownNow()
方法,都是无法关闭线程池的。
总结
ThreadPoolExecutor
的使用总结如下。
- 通过
ThreadPoolExecutor
的execute()
方法能够执行Runnable
任务; - 通过
ThreadPoolExecutor
的submit()
方法能够执行Runnable
任务和Callable
任务,并且能够获取异步的执行结果; ThreadPoolExecutor
的submit()
方法会返回一个Future
对象(实际就是FutureTask
),如果任务执行过程中发生了异常且未捕获,那么可以通过Future
的get()
方法感知到异常;ThreadPoolExecutor
的submit()
方法无论是提交Runnable
任务还是Callable
任务,都是将任务封装成了RunnableFuture
接口的子类FutureTask
,然后调用ThreadPoolExecutor
的execute()
方法来执行FutureTask
;- 关闭线程池时,如果运行的任务可以在有限时间内运行完毕,那么可以使用
shutdown()
方法来关闭线程池,这能够保证在关闭线程池时,正在运行的任务会顺利运行完毕; - 关闭线程池时,如果运行的任务永远不会结束但是响应中断,那么可以使用
shutdownNow()
方法来关闭线程池,这种方式不保证任务顺利运行完毕; - 如果任务永远不会结束且不响应中断,那么无论是
shutdown()
方法还是shutdownNow()
方法,都无法关闭线程池。