一、写在开头
在上一篇文章我们写《Java并发编程之线程池十八问》的时候,鉴于当时的篇幅已经过长,很多内容就没有扩展了,在这篇文章里对一些关键知识点进行对比补充。
二、Runnable vs Callable
在创建线程的时候,一般会选用 Runnable 和 Callable 两种方式。
【源码对比】
Runnable接口
@FunctionalInterface
public interface Runnable {
/**
* 被线程执行,没有返回值也无法抛出异常
*/
public abstract void run();
}
Callable接口
@FunctionalInterface
public interface Callable<V> {
/**
* 计算结果,或在无法这样做时抛出异常。
* @return 计算得出的结果
* @throws 如果无法计算结果,则抛出异常
*/
V call() throws Exception;
}
- Runnable自 Java 1.0 以来一直存在,Callable在 Java 1.5 时引入;
- Runnable 接口不会返回结果或抛出检查异常,Callable 接口可以;
- Callable支持泛型,可定义返回值类型,但一般情况下没有返回值时,我们推荐使用Runnable接口,使得代码更简洁!
- 工具类 Executors 可以实现将 Runnable 对象转换成 Callable 对象。(
Executors.callable(Runnable task) 或 Executors.callable(Runnable task, Object result)
)。
三、execute() vs submit()
在线程池中我们有两种提交任务的方式,分别是 execute() 和 submit(),虽然我们在上一篇文章中都有用到,但是并没对它们的特点进行总结,这里做一个对比:
- execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
- submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值。
//这里使用Executors只是方便测试,正常使用时推荐使用ThreadPoolExecutor!
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<String> submit = executorService.submit(() -> {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "javabuild";
});
String s = submit.get();
System.out.println(s);
executorService.shutdown();
输出:
javabuild
如果一直没有获取到返回结果,会报错,使用get(long timeout,TimeUnit unit)方法的话,如果在 timeout 时间内任务还没有执行完,就会抛出 java.util.concurrent.TimeoutException。
四、shutdown() vs shutdownNow()
在JDK 1.8 中,线程池的停止一般使用 shutdown()、shutdownNow()这两种方法。
方法一: shutdown()
public void shutdown() {
final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁
mainLock.lock(); // 加锁以确保独占访问
try {
checkShutdownAccess(); // 检查是否有关闭的权限
advanceRunState(SHUTDOWN); // 将执行器的状态更新为SHUTDOWN
interruptIdleWorkers(); // 中断所有闲置的工作线程
onShutdown(); // ScheduledThreadPoolExecutor中的挂钩方法,可供子类重写以进行额外操作
} finally {
mainLock.unlock(); // 无论try块如何退出都要释放锁
}
tryTerminate(); // 如果条件允许,尝试终止执行器
}
在shutdown的源码中,会启动一次顺序关闭,在这次关闭中,执行器不再接受新任务,但会继续处理队列中的已存在任务,当所有任务都完成后,线程池中的线程会逐渐退出。
方法二: shutdown()
/**
* 尝试停止所有正在执行的任务,停止处理等待的任务,
* 并返回等待处理的任务列表。
*
* @return 从未开始执行的任务列表
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks; // 用于存储未执行的任务的列表
final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁
mainLock.lock(); // 加锁以确保独占访问
try {
checkShutdownAccess(); // 检查是否有关闭的权限
advanceRunState(STOP); // 将执行器的状态更新为STOP
interruptWorkers(); // 中断所有工作线程
tasks = drainQueue(); // 清空队列并将结果放入任务列表中
} finally {
mainLock.unlock(); // 无论try块如何退出都要释放锁
}
tryTerminate(); // 如果条件允许,尝试终止执行器
return tasks; // 返回队列中未被执行的任务列表
}
与shutdown不同的是shutdownNow会尝试终止所有的正在执行的任务,清空队列,停止失败会抛出异常,并且返回未被执行的任务列表。
五、isTerminated() vs isShutdown()
- isShutDown 当调用 shutdown() 或shutdownNow()方法后返回为 true;
- isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true;当调用shutdownNow()方法后,成功停止后返回true;
- 当线程池任务都正常完成的话,则这两种方法均为false。
六、结尾彩蛋
如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!
如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!