• 关于《Java并发编程之线程池十八问》的补充内容


    一、写在开头

    在上一篇文章我们写《Java并发编程之线程池十八问》的时候,鉴于当时的篇幅已经过长,很多内容就没有扩展了,在这篇文章里对一些关键知识点进行对比补充。

    二、Runnable vs Callable

    在创建线程的时候,一般会选用 RunnableCallable 两种方式。

    【源码对比】

    Runnable接口

    @FunctionalInterface
    public interface Runnable {
    /**
    * 被线程执行,没有返回值也无法抛出异常
    */
    public abstract void run();
    }

    Callable接口

    @FunctionalInterface
    public interface Callable {
    /**
    * 计算结果,或在无法这样做时抛出异常。
    * @return 计算得出的结果
    * @throws 如果无法计算结果,则抛出异常
    */
    V call() throws Exception;
    }
    1. Runnable自 Java 1.0 以来一直存在,Callable在 Java 1.5 时引入;
    2. Runnable 接口不会返回结果或抛出检查异常,Callable 接口可以;
    3. Callable支持泛型,可定义返回值类型,但一般情况下没有返回值时,我们推荐使用Runnable接口,使得代码更简洁!
    4. 工具类 Executors 可以实现将 Runnable 对象转换成 Callable 对象。(Executors.callable(Runnable task) 或 Executors.callable(Runnable task, Object result))。

    三、execute() vs submit()

    在线程池中我们有两种提交任务的方式,分别是 execute()submit(),虽然我们在上一篇文章中都有用到,但是并没对它们的特点进行总结,这里做一个对比:

    1. execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
    2. submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值。
    //这里使用Executors只是方便测试,正常使用时推荐使用ThreadPoolExecutor!
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    Future 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 shutdownNow() {
    List 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()

    1. isShutDown 当调用 shutdown() 或shutdownNow()方法后返回为 true;
    2. isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true;当调用shutdownNow()方法后,成功停止后返回true;
    3. 当线程池任务都正常完成的话,则这两种方法均为false。

    六、结尾彩蛋

    如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

    如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

  • 相关阅读:
    【Spring系列03】依赖注入(DI)[之set注入]
    elasticsearch安装
    搭建深度学习网络时节约GPU显存的技巧
    新手如何学习RPA,怎么学,从哪下手,学习资源哪里来?
    python unittest 基本用法
    【Spark NLP】第 11 章:词嵌入
    基于PHP的宠物爱好者交流平台管理系统设计与实现(源码+lw+部署文档+讲解等)
    java自定义注解及其使用
    数学建模国赛C蔬菜类商品的自动定价与补货决策C
    C语言--tips1
  • 原文地址:https://www.cnblogs.com/JavaBuild/p/18223767