当前位置:首页 > 技术 > 正文内容

Java 多线程编程核心概念与实现机制

访客 技术 2026年5月28日 1

进程与线程的本质差异

在深入探讨 Java 多线程之前,必须厘清进程(Process)与线程(Thread)在操作系统层面的核心差异。简而言之,进程是操作系统进行资源分配和保护的基本单位,而线程是 CPU 调度和执行的基本单位

以一个常见的桌面应用为例:启动一个浏览器即创建了一个进程,该进程拥有独立的内存空间。而在浏览器内部,渲染页面、执行 JavaScript、处理网络请求等工作则由不同的线程并发执行。进程为线程提供了运行所需的资源环境,一个进程内部可以包含多个共享该进程资源的线程。

Java 线程的生命周期与状态流转

在 Java 中,线程的执行是一个动态过程。自 Java 5 引入 java.lang.Thread.State 枚举后,Java 线程的生命周期被精确划分为以下六种状态:

线程生命周期状态图

  • NEW(新建):线程对象已被实例化,但尚未调用 start() 方法。
  • RUNNABLE(可运行):调用 start() 后进入此状态。它包含了传统操作系统理论中的"就绪"和"运行"状态。此时线程可能正在执行,也可能在等待操作系统的 CPU 时间片。
  • BLOCKED(阻塞):线程正在等待获取排他锁(如进入 synchronized 代码块或方法),以重新进入临界区。
  • WAITING(无限期等待):线程调用了 Object.wait()Thread.join()LockSupport.park() 等方法,进入等待状态,直到被其他线程显式唤醒。
  • TIMED_WAITING(限期等待):与 WAITING 类似,但带有超时参数。例如调用 Thread.sleep(long)Object.wait(long) 等,超时后会自动唤醒并重新进入 RUNNABLE 状态。
  • TERMINATED(终止):线程的 run() 方法执行完毕,或因未捕获的异常而意外终止。

线程调度与优先级机制

Java 允许开发者通过优先级来向线程调度器提供调度建议。线程优先级通过 1 到 10 的整数表示,定义在 Thread 类中:

  • MIN_PRIORITY:最低优先级(值为 1)。
  • NORM_PRIORITY:默认优先级(值为 5)。
  • MAX_PRIORITY:最高优先级(值为 10)。

可以通过 setPriority()getPriority() 方法进行修改和读取。需要注意的是,Java 线程优先级最终会映射到操作系统的线程优先级上。由于不同操作系统(如 Windows 和 Linux)的调度策略和优先级层级不同,过度依赖 Java 线程优先级可能会导致程序在不同平台上表现出不一致的跨平台行为。高优先级仅意味着获取 CPU 时间片的概率更大,绝不保证严格的执行顺序。此外,子线程默认会继承父线程的优先级。

多线程的实现方案

在 Java 中,创建并发执行单元主要有以下三种标准方式:

1. 实现 Runnable 接口(推荐)

将"要执行的任务"与"线程控制机制"解耦是现代 Java 编程的最佳实践。通过实现 Runnable 接口,类只需关注任务逻辑(即 run() 方法),而将线程的生命周期管理交由 Thread 类处理。

public class TaskExecutor {
    public static void main(String[] args) {
        // 定义任务逻辑
        Runnable downloadTask = () -> {
            String taskName = Thread.currentThread().getName();
            System.out.println(taskName + " 开始执行下载任务...");
            try {
                for (int progress = 10; progress <= 100; progress += 10) {
                    System.out.println(taskName + " 下载进度: " + progress + "%");
                    Thread.sleep(100); // 模拟耗时操作
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println(taskName + " 被中断");
            }
            System.out.println(taskName + " 下载完成");
        };

        // 将任务分配给不同的线程执行
        Thread worker1 = new Thread(downloadTask, "Worker-Alpha");
        Thread worker2 = new Thread(downloadTask, "Worker-Beta");

        worker1.start();
        worker2.start();
    }
}

2. 继承 Thread 类

直接继承 Thread 类并重写 run() 方法是另一种传统方式。这种方式编写起来较为直接,且在类内部可以直接使用 this 来引用当前线程对象。但由于 Java 不支持多继承,这种方式会限制类的扩展能力。

public class DataProcessor extends Thread {
    private final String dataSource;

    public DataProcessor(String name, String dataSource) {
        super(name);
        this.dataSource = dataSource;
    }

    @Override
    public void run() {
        System.out.println(this.getName() + " 正在处理来自 " + dataSource + " 的数据...");
        try {
            // 模拟数据处理过程
            Thread.sleep(200);
            System.out.println(this.getName() + " 数据处理完毕");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        DataProcessor processorA = new DataProcessor("Processor-A", "Database");
        DataProcessor processorB = new DataProcessor("Processor-B", "MessageQueue");

        processorA.start();
        processorB.start();
    }
}

3. 使用 Callable 与 Future(支持返回值)

前两种方式均无法直接获取线程执行的返回结果,也无法抛出受检异常。Callable 接口配合 FutureTask 解决了这一痛点,适用于需要异步获取计算结果的场景。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class AsyncCalculator {
    public static void main(String[] args) throws Exception {
        // 定义带有返回值的计算任务
        Callable<Integer> sumTask = () -> {
            int sum = 0;
            for (int i = 1; i <= 100; i++) {
                sum += i;
            }
            Thread.sleep(100); // 模拟计算延迟
            return sum;
        };

        // 包装为 FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(sumTask);
        Thread calcThread = new Thread(futureTask, "Calc-Thread");
        calcThread.start();

        // 主线程可以继续执行其他操作,随后获取结果(此操作会阻塞直到结果返回)
        System.out.println("主线程正在等待计算结果...");
        Integer result = futureTask.get();
        System.out.println("1 到 100 的累加和为: " + result);
    }
}

实现方案的技术选型对比

  • 架构设计:实现 Runnable(或 Callable)接口符合"组合优于继承"的设计原则,将任务逻辑与线程运行机制彻底解耦,更利于后续接入线程池(Executor Framework)。
  • 扩展性:Java 采用单继承模型,继承 Thread 类会导致该类无法再继承其他业务基类;而实现接口则保留了继承的灵活性。
  • 资源共享:多个线程可以共享同一个 Runnable 实例,非常适合处理多个线程操作同一份资源的场景;而继承 Thread 通常需要借助静态变量或外部引用来实现资源共享,增加了代码复杂度。
  • 返回值与异常:只有 Callable 能够原生支持返回执行结果并抛出异常,是构建复杂异步流水线的基石。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。