Java newSingleThreadExecutor 顺序执行与主线程同步

Java newSingleThreadExecutor 顺序执行与主线程同步

本文深入探讨了`executors.newsinglethreadexecutor()`在保证任务顺序执行方面的特性,并解释了为何在多线程环境下,主线程的输出可能与单线程执行器中任务的输出顺序不一致。核心在于主线程与执行器线程的独立性。文章提供了标准的`shutdownandawaittermination`模式,以确保主线程等待所有提交任务完成后再继续执行,从而实现预期的输出顺序和正确的资源管理。

理解 Executors.newSingleThreadExecutor() 的执行特性

Executors.newSingleThreadExecutor() 创建一个使用单个工作线程的 ExecutorService。其核心保证是:所有提交到此执行器的任务都将按照它们被提交的顺序,在一个单独的线程中依次执行。这意味着,如果你提交了任务A、B、C,那么它们一定会以A -> B -> C的顺序被该工作线程执行,绝不会并发或乱序。

然而,这种顺序保证仅限于执行器内部的任务队列。它并不意味着提交任务的主线程会等待这些任务执行完毕。主线程在提交任务后会立即继续执行其自身的代码,而执行器中的任务则在后台异步执行。这种行为可能导致主线程的输出与执行器任务的输出在控制台上的顺序出现“错位”。

考虑以下示例代码:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ZooInfo {    public static void main(String[] args) {        ExecutorService executorService = null;        Runnable runnable1 = () -> System.out.println("Printing zoo inventory");        Runnable runnable2 = () -> {            for (int i = 0; i < 3; i++) {                System.out.println("Printing record " + i);            }        };        try {            executorService = Executors.newSingleThreadExecutor();            System.out.println("Begin"); // 主线程输出            executorService.execute(runnable1);            executorService.execute(runnable2);            executorService.execute(runnable1);            System.out.println("End."); // 主线程输出        } finally {            if (executorService != null) {                executorService.shutdown();            }        }    }}

运行上述代码,你可能会观察到类似以下的输出:

立即学习“Java免费学习笔记(深入)”;

BeginEnd.Printing zoo inventoryPrinting record 0Printing record 1Printing record 2Printing zoo inventory

尽管 runnable1 和 runnable2 任务在执行器内部是按提交顺序执行的,但主线程的 System.out.println(“End.”) 却在这些任务的输出之前显示。这并非 newSingleThreadExecutor() 没有保证顺序,而是主线程没有等待执行器完成其工作。

行者AI 行者AI

行者AI绘图创作,唤醒新的灵感,创造更多可能

行者AI 100 查看详情 行者AI

解决方案:等待执行器任务完成

为了确保主线程在所有提交的任务完成后再继续执行(例如打印“End.”),我们需要一种机制来同步主线程和执行器线程。ExecutorService 提供了 shutdown() 和 awaitTermination() 方法来实现这一目的。

shutdown(): 此方法会启动一个有序的关闭过程,即不再接受新的任务,但会完成所有已提交的任务。awaitTermination(long timeout, TimeUnit unit): 此方法会阻塞当前线程,直到所有任务执行完毕、超时发生或者当前线程被中断。

将这两个方法结合起来,形成一个标准的“关闭并等待终止”模式,可以有效解决主线程与执行器线程的同步问题。

示例代码:使用 shutdownAndAwaitTermination

通常,我们会封装一个辅助方法来实现 shutdownAndAwaitTermination 逻辑:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class ZooInfoCorrected {    public static void main(String[] args) {        ExecutorService executorService = null;        Runnable runnable1 = () -> System.out.println("Printing zoo inventory");        Runnable runnable2 = () -> {            for (int i = 0; i < 3; i++) {                System.out.println("Printing record " + i);            }        };        try {            executorService = Executors.newSingleThreadExecutor();            System.out.println("Begin");            executorService.execute(runnable1);            executorService.execute(runnable2);            executorService.execute(runnable1);            // 关键步骤:等待执行器任务完成            shutdownAndAwaitTermination(executorService);            System.out.println("End.");        } finally {            // 确保执行器被关闭,即使在等待过程中发生异常            // 如果shutdownAndAwaitTermination已经调用了shutdown,这里可以省略,            // 但为了健壮性,在finally中再次检查并调用是安全的。            if (executorService != null && !executorService.isShutdown()) {                executorService.shutdown();            }        }    }    /**     * 关闭ExecutorService并等待其所有任务完成。     * 这是一个推荐的boilerplate模式。     *     * @param pool 要关闭的ExecutorService     */    static void shutdownAndAwaitTermination(ExecutorService pool) {        pool.shutdown(); // 禁用新任务提交        try {            // 等待已提交任务在指定超时时间内完成            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {                pool.shutdownNow(); // 如果超时,则强制关闭                // 等待任务响应中断                if (!pool.awaitTermination(60, TimeUnit.SECONDS))                    System.err.println("Pool did not terminate");            }        } catch (InterruptedException ie) {            // (可选) 重新中断当前线程            Thread.currentThread().interrupt();            // 如果当前线程在等待时被中断,则强制关闭            pool.shutdownNow();            System.err.println("Current thread interrupted while waiting for pool termination.");        }    }}

通过在 executorService.execute(runnable1); 之后调用 shutdownAndAwaitTermination(executorService);,主线程会阻塞,直到执行器中的所有任务完成,或者达到超时时间。这样,”End.” 就会在所有任务输出之后打印:

BeginPrinting zoo inventoryPrinting record 0Printing record 1Printing record 2Printing zoo inventoryEnd.

注意事项与最佳实践

finally 块中的 shutdown(): 即使在 shutdownAndAwaitTermination 方法中已经调用了 shutdown(),在 main 方法的 finally 块中再次检查并调用 shutdown() 仍然是一个好的实践,以防在 try 块中其他地方发生异常导致 shutdownAndAwaitTermination 未被调用。超时处理: awaitTermination 接受一个超时参数。设定一个合理的超时时间非常重要,以防止程序无限期阻塞。如果超时发生,shutdownNow() 可以尝试中断正在执行的任务并清空任务队列。InterruptedException: 在等待终止期间,如果当前线程被中断,awaitTermination 会抛出 InterruptedException。在这种情况下,通常应该重新中断当前线程(Thread.currentThread().interrupt())并清理资源,例如调用 shutdownNow()。Project Loom 与结构化并发: Java 19 引入的 Project Loom (光纤) 和其孵化中的结构化并发(Structured Concurrency)特性,旨在简化并发编程,尤其是在处理一组相关任务时。结构化并发可能提供更直观的方式来确保一组任务作为一个整体完成,而无需手动管理 ExecutorService 的生命周期和等待逻辑。虽然这不直接替代 shutdownAndAwaitTermination 在现有 ExecutorService 模型中的作用,但它代表了Java并发未来发展的一个方向。

总结

Executors.newSingleThreadExecutor() 确实保证了其内部任务的顺序执行。然而,当观察到主线程的输出与执行器任务的输出顺序不符时,这通常是因为主线程没有等待执行器完成其工作。通过采用标准的 shutdownAndAwaitTermination 模式,我们可以有效地同步主线程与执行器线程,确保所有后台任务完成后,主线程再继续执行,从而实现预期的程序行为和输出顺序。正确管理 ExecutorService 的生命周期是编写健壮并发应用程序的关键。

以上就是Java newSingleThreadExecutor 顺序执行与主线程同步的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/309052.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月5日 04:28:00
下一篇 2025年11月5日 04:28:54

相关推荐

  • js如何判断字符串包含子串 字符串包含检测的3种实用技巧

    判断javascript字符串是否包含子串,主要有三种方法:1.includes() 方法最直观且推荐,返回布尔值表示是否包含指定子串;2.indexof() 方法通过返回索引或 -1 判断是否包含,需额外比较操作;3.正则表达式 test() 方法更灵活,支持复杂模式匹配。选择依据具体需求:简单查…

    2025年12月20日 好文分享
    000
  • js中判断多个值是否满足条件的写法

    在javascript中判断多个值是否满足条件的最合适的写法取决于具体场景:1. 条件少且固定时使用&&运算符最为直接;2. 条件多且动态变化时推荐array.every()方法;3. 需要更多控制权如提前退出则选择循环;4. 在函数式编程场景下可使用reduce但需注意可读性;此外…

    2025年12月20日 好文分享
    000
  • js性能optimize优化_js性能optimize提升技巧

    js性能优化是通过改进代码和资源管理提升javascript执行效率和用户体验。1.减少重排重绘,批量更新dom并使用documentfragment提升操作效率;2.优先使用css transform实现动画以利用gpu加速;3.合并文件与使用css sprites降低http请求数量;4.避免内…

    2025年12月20日 好文分享
    000
  • js中多个条件需要并行判断怎么写

    在javascript中,处理多个独立条件并行判断的高效方法包括使用promise.all或promise.allsettled进行异步判断,使用array.every或array.some进行同步判断,以及通过if…else if…else结构实现多路分支。1. 异步条件下…

    2025年12月20日 好文分享
    000
  • js如何解析CAD文件 前端CAD图纸预览方案实现

    纯js直接解析#%#$#%@%@%$#%$#%#%#$%@_b5fde512c++76571c8afd6a6089eaaf42a文件难度较大,但可通过替代方案实现前端预览。常用方法包括:1.服务端转换,利用专业库将cad转为svg/pdf等格式,前端展示结果;2.使用webassembly运行c/c…

    2025年12月20日 好文分享
    000
  • js如何实现水印效果 前端页面水印生成与防护方案

    前端水印的实现主要有两种思路:canvas和dom结构。1.canvas方式性能较好,适合批量生成水印,但内容难以被选中和复制;2.dom结构方式允许用户选中和复制水印内容,但性能相对较差且可能影响页面渲染。为了防止水印被移除,可以采用定时重绘、mutationobserver监听、服务端渲染水印、…

    2025年12月20日 好文分享
    000
  • JavaScript怎样监听页面加载?

    domcontentloaded事件在dom解析完成后触发,适合操作dom;load事件在所有资源加载后触发,适合依赖外部资源的操作。监听页面加载的方法有:1.domcontentloaded事件,用于快速响应dom就绪状态;2.load事件,确保所有资源加载完成;3.使用readystate属性,…

    2025年12月20日 好文分享
    000
  • 如何用JavaScript动态改变图片的src属性?

    处理图片加载失败的策略有:1.使用onerror事件替换为默认错误图片;2.隐藏图片元素避免显示破碎图标;3.提供文字提示并移除onerror事件防止无限循环。优化用户体验的方法包括预加载图片以避免闪烁、显示加载指示器给予用户反馈、使用css过渡效果提升视觉体验。更高级的图片处理方式有:1.利用sr…

    2025年12月20日 好文分享
    000
  • 怎样用JavaScript实现一个简单的表单验证?

    表单验证重要性在于提升用户体验并减轻服务器压力。它能即时反馈输入错误,避免无效数据直接提交至后端,是系统的第一道防线。常见验证类型包括:1. 必填项验证,确保关键字段不为空;2. 格式验证,如邮箱、手机号需符合特定正则表达式;3. 长度验证,限制最小或最大输入长度;4. 范围验证,针对数字的合法区间…

    2025年12月20日 好文分享
    000
  • js如何实现热力图 基于Leaflet的热力地图生成

    要使用js和leaflet实现热力图,主要分五步:1. 引入leaflet和热力图插件;2. 创建leaflet地图并设置中心点和缩放级别;3. 准备包含经纬度和权重的热力图数据;4. 创建热力图层并配置参数如半径、透明度和模糊度;5. 将热力图层添加到地图上。为优化性能,可采用数据聚合、调整渲染参…

    2025年12月20日 好文分享
    000
  • 如何用JavaScript动态改变网页主题颜色?

    要实现动态改变网页主题颜色,核心方法是使用css变量配合javascript操作变量值。1.首先在css的:root中定义颜色变量如–primary-color、–background-color等;2.html中创建按钮作为切换触发器并设置data-theme属性;3.ja…

    2025年12月20日 好文分享
    000
  • js怎样操作CSS Houdini特性 6个Houdini API突破样式限制

    css houdini通过多个api让javascript直接操作浏览器渲染引擎,实现更灵活的样式控制。1. custom properties and values api支持定义带类型的css变量并进行动画;2. typed om api提供带单位的数值对象,提升性能和安全性;3. css pa…

    2025年12月20日 好文分享
    000
  • js中switch语句如何替代多个if判断

    Switch 语句提供了一种更清晰、更高效的方式来处理多个条件判断,尤其是在条件基于同一个变量的不同值时。它避免了冗余的 if…else if…else 结构,使代码更易读、易维护。 解决方案 在 JavaScript 中,switch 语句通过比较一个表达式的值与多个 case 标签的值,…

    2025年12月20日 好文分享
    000
  • js如何检测摄像头权限 判断摄像头状态的3种检测方案!

    要检测摄像头权限并判断状态,首先使用navigator.mediadevices.getusermedia请求权限,并通过readystate属性判断摄像头是否可用。具体步骤如下:1. 请求权限时处理兼容性问题,尝试使用mediadevices.getusermedia,必要时回退旧方法。2. 处理…

    2025年12月20日 好文分享
    000
  • js如何实现节流函数 节流函数的2种高效写法解析

    节流函数用于控制函数执行频率,避免频繁触发导致性能问题。其核心是平衡响应速度与资源消耗,主要有时间戳和定时器两种实现方式:1. 时间戳版本立即执行但可能忽略最后一次触发;2. 定时器版本延迟执行但确保最后一次触发一定执行;3. 可结合两者实现更完善但复杂的节流逻辑。在react中可通过useref和…

    2025年12月20日 好文分享
    000
  • js如何检测USB设备 WebUSB接口调用方法解析

    要检测usb设备并实现javascript与硬件交互,可通过webusb api实现。首先调用navigator.usb.requestdevice()请求权限并获取设备对象;接着通过device.open()、selectconfiguration()和claiminterface()连接设备;然…

    2025年12月20日 好文分享
    000
  • js如何实现运动检测 视频运动物体检测技术实现

    运动检测可通过逐帧比较像素差异实现。首先获取视频帧并转为像素数组;其次比较连续帧rgb值差异,超阈值则标记为运动像素;最后用canvas覆盖标记。应对光线变化可用hsv颜色空间忽略亮度或用背景建模。优化性能可降低分辨率、使用web workers或tracking.js库。区分摄像头抖动可用光流法或…

    2025年12月20日
    000
  • JavaScript中如何实现数组分组?

    javascript中实现数组分组可通过多种方式,1.使用reduce方法:通过遍历数组将元素按规则累积到结果对象中;2.使用foreach方法:逻辑类似但无需手动返回累加器;3.使用map对象:可保持键的插入顺序。此外,分组依据支持复杂逻辑时可用函数定义分组规则,且分组后可进一步对每组数据进行聚合…

    2025年12月20日 好文分享
    000
  • js怎么实现前端日志记录 浏览器日志采集与上报

    前端日志记录的核心在于捕获错误、格式化日志信息并安全上报。1.使用window.onerror、try…catch和unhandledrejection捕获全局错误、特定代码块错误及promise rejection;2.通过formatlog定义日志格式,包含时间戳、日志级别、用户信息…

    2025年12月20日 好文分享
    000
  • JS怎样实现前端路由拦截 5种路由拦截方案保障前端页面权限

    前端路由拦截的核心是通过在路由跳转前后进行权限判断,决定用户是否能访问特定页面。常见实现方式包括:1. 全局路由守卫(如 vue 的 beforeeach 或 react 的 usenavigate 钩子),集中管理权限并控制跳转;2. 组件级别权限控制(如 react 的 hoc),灵活控制单个组…

    2025年12月20日 好文分享
    000

发表回复

登录后才能评论
关注微信