0

0

掌握Java中通过用户输入优雅终止无限循环的并发编程实践

DDD

DDD

发布时间:2025-10-29 18:00:07

|

171人浏览过

|

来源于php中文网

原创

掌握Java中通过用户输入优雅终止无限循环的并发编程实践

本教程详细探讨了在java中如何利用多线程和非阻塞输入机制,实现一个可由用户输入(如回车键)中断的无限循环,同时运行如加载动画等并发任务。文章解释了传统阻塞式输入方法的局限性,并提供了一个基于`volatile`标志和`inputstream.available()`的完整解决方案,确保动画流畅运行的同时,能及时响应用户中断指令,从而提高程序的交互性和用户体验。

在Java应用程序开发中,我们经常会遇到需要执行一个持续性任务(如加载动画、数据监听)直到用户发出停止指令的场景。一个常见的挑战是,如何既能保持任务的持续性,又能实时响应用户的输入,特别是当用户输入是用于中断任务时。本文将深入探讨这一问题,并提供一个基于多线程和非阻塞输入的高效解决方案。

传统阻塞式输入方法的局限性

考虑一个常见的需求:显示一个循环的加载动画(例如,三个点“...”不断闪烁),直到用户按下回车键才停止。初学者可能会尝试在一个无限循环中显示动画,并在循环内部或之后调用System.in.read()来等待用户输入。

public class BlockingLoopExample {

    public static void pause(long duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 重新设置中断标志
        }
    }

    public static void loading() {
        while (true) { // 尝试无限循环显示动画
            pause(500);
            for (int i = 0; i < 3; i++) {
                System.out.print(".");
                pause(500);
            }
            System.out.print("\b\b\b"); // 回退光标,清除点
        }
    }

    public static void main(String[] args) {
        System.out.println("Loading... Press Enter to stop.");
        loading(); // 动画开始
        // 理论上这里应该等待输入,但实际上永远不会执行到这里
        try {
            System.in.read(); // 阻塞等待输入
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("\nStopped.");
    }
}

上述代码存在两个主要问题:

  1. 阻塞问题: loading()方法内部是一个无限循环while(true),这意味着程序将永远停留在loading()方法中,main方法中System.in.read()那一行代码永远不会被执行到。因此,用户无法通过输入来停止动画。
  2. 并发需求: 即便我们将System.in.read()放在loading()循环内部,System.in.read()本身是一个阻塞调用。这意味着一旦程序执行到System.in.read(),它就会暂停,直到用户输入数据。这会导致动画停止,无法实现动画与输入监听的并发进行。

为了解决这些问题,我们需要引入多线程编程和非阻塞输入的概念。

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

解决方案:多线程与非阻塞输入

实现动画与输入监听并发进行并优雅终止无限循环的关键在于:

  1. 使用单独的线程处理动画: 将动画逻辑封装在一个独立的线程中运行。
  2. 使用单独的线程监听输入: 另一个线程专门负责监听用户的输入。
  3. 共享状态与volatile关键字: 两个线程需要通过一个共享的标志(例如一个boolean变量)来通信。当输入线程检测到用户输入时,它会修改这个标志,动画线程则定期检查这个标志以决定是否停止。为了确保不同线程对共享变量的可见性,这个标志必须声明为volatile。
  4. 非阻塞输入检查: 输入监听线程不应使用阻塞式的System.in.read()。相反,它应该使用InputStream.available()方法来检查输入缓冲区中是否有数据可用,而不会阻塞当前线程。

下面是基于这些原则的完整解决方案代码:

Lessie AI
Lessie AI

一款定位为「People Search AI Agent」的AI搜索智能体

下载
import java.io.IOException;
import java.io.InputStream;

public class ConcurrentLoadingAnimation {

    // 使用 volatile 关键字确保 stopFlag 对所有线程的可见性
    private static volatile boolean stopFlag = false;

    /**
     * 模拟暂停一段时间的方法
     * @param duration 暂停时长(毫秒)
     */
    public static void pause(long duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            // 当线程被中断时,设置中断标志并退出,以便外部可以捕获中断
            Thread.currentThread().interrupt();
            System.err.println("Loading thread interrupted.");
            stopFlag = true; // 强制停止动画
        }
    }

    /**
     * 运行加载动画的线程任务
     */
    static class LoadingTask implements Runnable {
        @Override
        public void run() {
            System.out.println("Loading... Press Enter to stop.");
            while (!stopFlag) { // 只要 stopFlag 为 false,就继续循环
                for (int i = 0; i < 3; i++) {
                    if (stopFlag) break; // 每次打印前检查是否需要停止
                    System.out.print(".");
                    pause(300); // 调整暂停时间以适应动画速度
                }
                if (stopFlag) break; // 循环结束后再次检查
                System.out.print("\b\b\b   \b\b\b"); // 回退光标,清除点并留空,再回退
                pause(300);
            }
            // 动画停止后,清除可能残留的点
            System.out.print("\r                     \r"); // 清除整行并回车
            System.out.println("Loading stopped.");
        }
    }

    /**
     * 监听用户输入的线程任务
     */
    static class InputMonitorTask implements Runnable {
        @Override
        public void run() {
            try (InputStream in = System.in) { // 使用 try-with-resources 确保 InputStream 关闭
                while (!stopFlag) { // 只要 stopFlag 为 false,就继续监听
                    if (in.available() > 0) { // 检查输入缓冲区是否有数据
                        // 读取所有可用的输入,直到遇到换行符或缓冲区清空
                        while (in.available() > 0) {
                            int charCode = in.read();
                            if (charCode == '\n' || charCode == '\r') { // 检测到回车键
                                stopFlag = true; // 设置停止标志
                                break;
                            }
                        }
                    }
                    pause(50); // 短暂暂停,避免CPU空转过高
                }
            } catch (IOException e) {
                System.err.println("Error reading from input: " + e.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        // 创建并启动加载动画线程
        Thread loadingThread = new Thread(new LoadingTask(), "Loading-Animation-Thread");
        loadingThread.start();

        // 创建并启动输入监听线程
        Thread inputMonitorThread = new Thread(new InputMonitorTask(), "Input-Monitor-Thread");
        inputMonitorThread.start();

        // 主线程等待两个子线程完成
        try {
            loadingThread.join(); // 等待动画线程结束
            inputMonitorThread.join(); // 等待输入监听线程结束
        } catch (InterruptedException e) {
            System.err.println("Main thread interrupted.");
            Thread.currentThread().interrupt();
        }

        System.out.println("Program finished.");
    }
}

代码详解与注意事项

  1. volatile boolean stopFlag:

    • 这是一个共享变量,用于在InputMonitorTask和LoadingTask之间传递停止信号。
    • volatile关键字确保对stopFlag变量的修改能够立即被所有线程可见,避免了由于缓存不一致导致的同步问题。这是实现线程间通信的关键。
  2. LoadingTask (Runnable):

    • run()方法包含动画逻辑。
    • while (!stopFlag)循环是动画持续运行的条件。
    • 在每次打印点之前和循环结束之后,都会检查stopFlag。一旦stopFlag变为true,循环立即终止。
    • System.out.print("\b\b\b \b\b\b"):这行代码用于清除屏幕上的三个点。\b是退格符,它会将光标向前移动一个位置。连续使用\b可以清除字符。后面打印三个空格再用\b回退,是为了确保点被完全覆盖,且光标回到原始位置。
    • System.out.print("\r \r"): 在动画停止时,为了确保屏幕干净,使用回车符\r将光标移到行首,然后打印足够多的空格覆盖可能残留的动画字符,再用\r将光标移回行首。
  3. InputMonitorTask (Runnable):

    • try (InputStream in = System.in):使用Java 7引入的try-with-resources语句,确保System.in流在不再需要时能够被正确关闭(尽管System.in通常不建议关闭,但这里作为示例)。
    • in.available() > 0:这是实现非阻塞输入的关键。它返回输入流中可供读取的字节数。如果大于0,说明有数据输入,此时才尝试读取。
    • in.read():当available() > 0时,读取一个字节。
    • if (charCode == '\n' || charCode == '\r'):检测用户是否按下了回车键(在不同操作系统上,回车键可能产生\n或\r,或者两者都有)。一旦检测到,就将stopFlag设置为true,从而通知LoadingTask停止。
    • pause(50):在没有输入时,输入监听线程会短暂暂停,避免CPU空转,降低资源消耗。
  4. main方法:

    • 创建并启动LoadingTask和InputMonitorTask的两个Thread实例。
    • loadingThread.join()和inputMonitorThread.join():主线程会等待这两个子线程执行完毕。这意味着只有当stopFlag被设置为true,两个子线程都自然终止后,主线程才会继续执行并打印"Program finished."。

总结

通过上述多线程和非阻塞输入的方法,我们成功地解决了在Java中同时运行动画和监听用户输入的问题。这种模式在需要后台任务持续运行,同时需要用户随时介入终止的场景中非常有用。理解volatile关键字在线程间通信中的作用以及InputStream.available()的非阻塞特性,是构建响应式和高效并发Java应用程序的关键。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

838

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

742

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

737

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

399

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

0

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7.1万人学习

Java 教程
Java 教程

共578课时 | 48.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号