当前位置: 首页 > news >正文

【多线程】——基础篇

深入理解Java多线程:从进程到并发控制

在多核处理器成为主流的今天,有效地利用多线程技术是构建高性能、高响应应用程序的关键。本文将系统地阐述Java多线程编程中的核心概念与区别。

一、线程和进程的区别

这是理解多线程的基础。我们可以用一个生动的比喻来理解:

  • 进程: 一个独立的工厂。每个工厂都有自己独立的厂房、原材料仓库、电力系统(系统资源如内存、文件句柄等)。工厂之间相互隔离,一个工厂的火灾不会直接影响另一个工厂(进程间具有独立的内存空间,互不干扰)。切换工厂需要耗费很大成本(进程上下文切换开销大)。
  • 线程: 工厂中的工人。同一个工厂里的多个工人共享厂房、仓库和电力(同一进程内的多个线程共享进程的内存空间和资源)。工人们协同工作,沟通成本低(线程间通信简单,共享内存即可)。让一个工人停下换另一个工人工作,代价较小(线程上下文切换开销小)。

核心区别总结表:

特性进程线程
基本单位资源分配和调度的基本单位CPU调度和执行的基本单位
资源开销大(独立地址空间,创建、销毁、切换开销大)小(共享地址空间,创建、销毁、切换开销小)
内存共享进程间内存空间相互隔离,通信需要IPC机制线程共享所属进程的内存和资源,通信简单但需同步
健壮性高,一个进程崩溃不会影响其他进程低,一个线程崩溃可能导致整个进程崩溃
包含关系一个进程可以包含多个线程线程是进程的一部分
二、并行与并发的区别

这两个概念密切相关,但侧重点不同:

  • 并发: 逻辑上的同时发生。指在单核或多核CPU上,系统通过快速切换任务(时间片轮转),使得在一段时间内,多个任务都能得到推进。它解决的是“任务排队等待”的问题。

    • 例子: 单核CPU同时运行微信和Chrome浏览器。CPU先执行一段微信的代码,再快速切换到执行一段Chrome的代码,用户感觉两个程序在“同时”运行。
  • 并行: 物理上的同时执行。指在多核CPU上,多个任务在同一时刻真正地同时执行。它需要硬件支持,是并发的真子集。

    • 例子: 四核CPU,一个核心在计算Excel公式,一个核心在播放音乐,一个核心在渲染网页,它们是真正同时进行的。

核心关系: 并发是并行的超集。 并行是并发的一种特殊情况。我们编写多线程程序是为了实现并发,而程序能否并行执行取决于硬件条件。并发关注的是程序的设计与结构,并行关注的是程序的执行状态。

三、线程创建的方式:Runnable vs. Callable,start vs. run

1. 创建方式 Java有四种创建线程的方式:

  • 继承 Thread 类,重写 run() 方法。

  • 实现 Runnable 接口,实现 run() 方法(最常用)。

  • 实现 Callable 接口,实现 call() 方法。

  • 使用线程池(如 ExecutorService)。

2. Runnable 与 Callable 区别

特性RunnableCallable
方法签名void run()V call() throws Exception
返回值,返回泛型 V 类型的结果
异常处理必须在 run() 内部消化所有 checked Exception可以向上抛出异常,方便外部获取
应用场景简单的异步任务执行需要获取任务执行结果或抛出异常的场景
提交方式可提交给 Thread 或线程池 (execute)只能提交给线程池 (submit)

3. start() 与 run() 区别

这是一个经典的面试题,关键在于理解线程的生命周期。

  • thread.start()

    • 作用启动一个新线程。JVM会调用这个新线程的 run() 方法。
    • 结果两个线程并发运行:当前线程(调用start的线程)和另一个新线程(执行其run方法)。
  • thread.run()

    • 作用普通的方法调用。它不会启动新线程。
    • 结果仍在当前线程中,同步地执行 run() 方法中的代码。失去了多线程的意义。

结论: 启动线程必须调用 start() 方法。

四、线程的状态及状态变化

Java线程在其生命周期中处于以下6种状态之一(java.lang.Thread.State):

  1. NEW(新建):线程被创建,但尚未调用 start() 方法。
  2. RUNNABLE(可运行):调用 start() 后。注意:此状态包含了 正在CPU上运行(Running) 和 就绪(Ready,在等待CPU时间片) 两种子状态。
  3. BLOCKED(阻塞):线程等待获取一个监视器锁(synchronized锁) 而陷入阻塞。(其他线程释放锁后,JVM会随机选择一个此状态的线程变为RUNNABLE)
  4. WAITING(无限期等待):线程进入等待状态,需要被其他线程显式地唤醒。调用以下方法会进入此状态:
    • Object.wait() (需被 notify()/notifyAll() 唤醒)
    • Thread.join() (等待目标线程执行完毕)
    • LockSupport.park()
  5. TIMED_WAITING(限期等待):线程进入等待状态,但无需其他线程唤醒,时间一到自动唤醒。调用以下方法会进入此状态:
    • Thread.sleep(long millis)
    • Object.wait(int timeout)
    • Thread.join(int timeout)
    • LockSupport.parkNanos()
  6. TERMINATED(终止):线程执行完毕(run() 方法执行结束)或因异常退出。

状态转换图:

NEW --start()--> RUNNABLE <--(CPU时间片到/或IO完成)--> RUNNING
RUNNABLE --获取synchronized锁--> RUNNING
RUNNABLE --竞争synchronized锁失败--> BLOCKED
RUNNING --synchronized锁被释放--> RUNNABLERUNNING --wait()/join()--> WAITING
WAITING --notify()/notifyAll()/目标线程结束--> RUNNABLERUNNING --sleep(time)/wait(time)--> TIMED_WAITING
TIMED_WAITING --时间到--> RUNNABLERUNNING --run()方法结束/异常退出--> TERMINATED

小结:

五、线程按顺序执行:join、notify和notifyAll区别

1. join() thread.join() 方法用于等待目标线程执行完毕。调用此方法的线程(如main线程)会从 RUNNABLE 进入 WAITINGTIMED_WAITING 状态,直到目标线程 thread 终止,它才会继续执行。


Thread t1 = new Thread(() -> System.out.println("T1"));
Thread t2 = new Thread(() -> {try {t1.join(); // 等T1执行完} catch (InterruptedException e) {e.printStackTrace();}System.out.println("T2");
});
t1.start();
t2.start();
// 输出顺序永远是:T1 -> T2

2. notify() 和 notifyAll() 区别 这两个方法都用于唤醒因调用 Object.wait() 而进入 WAITINGTIMED_WAITING 状态的线程,但策略不同:

  • notify():随机唤醒一个正在等待此对象锁的线程。我们无法控制唤醒哪一个。
  • notifyAll():唤醒所有正在等待此对象锁的线程。这些被唤醒的线程会去竞争锁,抢到锁的线程会继续执行。

选择: 在绝大多数情况下,使用 notifyAll() 更安全。使用 notify() 可能导致“信号丢失”——如果唤醒的线程发现条件依然不满足而又自己wait了,而真正该被唤醒的线程却一直在等待,从而导致所有线程都永久等待(死锁)。

六、Java中wait和sleep方法的不同
特性Object.wait()Thread.sleep()
所属类Object 类Thread 类
调用前提必须同步代码块(synchronized) 中调用可以在任何地方调用
作用机制释放当前持有的对象锁不释放任何锁(包括synchronized和Lock)
用途用于线程间通信协调仅用于让当前线程暂停执行指定时间
唤醒方式只能由其他线程通过 notify()/notifyAll() 唤醒,或自己 interrupt()时间到期后自动唤醒,或被 interrupt()
异常抛出 InterruptedException抛出 InterruptedException

核心记忆点: wait()释放锁并用于线程间通信;sleep() 不释放锁,只是让线程睡觉。

七、如何停止一个正在运行的线程

停止一个线程不是一个简单的 stop() 操作(该方法已废弃,因为它强制停止,会导致数据不一致等严重后果)。正确的方式是协作式的,即通知目标线程“请你停下来”,由目标线程自己决定何时安全地结束。

核心机制:中断(Interruption)

  1. 标记中断位:每个线程都有一个 boolean 类型的中断标志。调用 thread.interrupt() 方法就是将该线程的中断标志设为 true

  2. 响应中断:被中断的线程需要周期性地检查自己的中断状态,并决定如何处理。

    • thread.isInterrupted():检查指定线程的中断状态,不清除标志位
    • Thread.interrupted():检查当前线程的中断状态,检查后会清除标志位(重置为false)。
  3. 处理中断异常:如果线程在 wait(), sleep(), join() 等过程中被中断,这些方法会立即抛出 InterruptedException,并且在抛出异常前,JVM会先将该线程的中断标志位清除(重置为false)

正确停止线程的示例:

public class StoppableThread implements Runnable {@Overridepublic void run() {// 1. 定期检查中断标志位while (!Thread.currentThread().isInterrupted()) {try {// 2. 执行任务...System.out.println("Working...");// 模拟耗时操作,此时若被中断会抛出InterruptedExceptionThread.sleep(1000);} catch (InterruptedException e) {// 3. 捕获异常,收到中断信号System.out.println("Thread was interrupted, exiting...");// 恢复中断状态(可选,让上层调用者也能知道发生了中断)Thread.currentThread().interrupt();//  break出循环,结束run方法,线程自然终止break;}}System.out.println("Thread stopped.");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new StoppableThread());thread.start();// 主线程睡眠3秒后,请求中断子线程Thread.sleep(3000);thread.interrupt(); // 发送中断请求}
}

另一种方式:使用 volatile 标志位 通过一个共享的 volatile boolean 变量来控制线程的终止。

public class VolatileStopThread implements Runnable {private volatile boolean cancelled = false;@Overridepublic void run() {while (!cancelled) {// 执行任务...}System.out.println("Thread stopped by flag.");}public void cancel() {cancelled = true;}
}

小结:

选择: 对于阻塞在 wait(), sleep() 等操作上的线程,中断机制是唯一能使其立即响应的方式。对于纯计算型的循环,两种方式都可以,但中断机制更为标准和强大。

希望这篇详尽的文章能帮助您彻底理解Java多线程的这些核心概念。理解这些区别和机制是编写正确、高效并发代码的基础。

http://www.dtcms.com/a/430979.html

相关文章:

  • 多语言网站 自助洛阳兼职网站
  • 【C++实战(61)】C++ 并发编程实战:解锁线程池的奥秘与实现
  • 外贸网站做开关行业的哪个好做网站用什么配置笔记本
  • 极路由 极1s J1S hc5661 刷入OpenWRT并设置同网段子路由
  • 帮传销组织做网站wordpress换域名安装
  • ubuntu 24.04 从 6.8 内核升级 6.11 网卡加载失败问题
  • 如何让网站gzipwordpress 站长
  • SQL——子查询
  • dw做的网站怎么传到网络上去腾度网站建设
  • [创业之路-643]:互联网与移动互联网行业与通信行业的关系
  • Easyx使用(下篇)
  • css`font-variant-numeric: tabular-nums` 用来控制数字的样式。
  • CentOS7二进制安装包方式部署K8S集群之ETCD集群部署
  • Python常用三方模块——Pillow
  • 友情下载网站外贸cms建站
  • 976. 三角形的最大周长
  • 该怎么跟程序员谈做网站自己怎么免费做网站
  • 基于岗位需求的康体项目策划与设计实训室规划
  • 大理做网站哪家好大概多少钱
  • Nest 中使用Swagger自动化API文档生成
  • 融合:迈向 “一台计算机” 的终极架构
  • ai手诊面诊抖音快手微信小程序看广告流量主开源
  • 网页设计制作手机网站网站做了301怎么查看跳转前网站
  • 安卓基础组件018--第三方Image库
  • 25.60 秒计时器,仅使用 HTML 和 CSS | CSS SVG 动画
  • 网站推广工作计划乌市网络营销公司
  • 微信小程序入门学习教程,从入门到精通,微信小程序常用API(下)——知识点详解 + 案例实战(5)
  • iptables vs nftables
  • 5.网站滚动动画效果 | CSS 与 JavaScript
  • 国家精品课程建设工作网站优设网logo设计