返回Notes

/ notes

关于并发编程(1)

并发/并行、进程线程、协程、JMM、线程状态与通信等

并发编程、多线程(1):基础与线程安全

并行与并发

  • 并行(Parallelism):多个任务在同一时刻真正执行,通常依赖多核 CPU 或多处理器。
  • 并发(Concurrency):多个任务在同一时间段内交替推进。单核 CPU 可借助时间片轮转实现;多核场景也同样存在并发。

并发描述任务组织与推进方式;并行描述是否真的同时执行。二者不互斥。

严格来说,一个 CPU 核心同一时刻通常只执行一个执行流。操作系统调度器通过时间片切换,让多个线程看起来同时运行。

进程与线程

  • 进程:操作系统分配资源的基本单位;可理解为运行中的应用程序。
  • 线程:CPU 调度的基本单位,是进程内的执行流。
  • 同一进程的线程共享堆、方法区、文件等资源;每个线程有独立的程序计数器、虚拟机栈和本地方法栈。

协程、虚拟线程与 Goroutine

  • 协程:用户态的轻量级执行单元,调度通常由语言运行时完成,切换成本低于内核线程。
  • Java 平台线程:通常与 OS 线程 1:1 映射。
  • Java 21 虚拟线程:由 JVM 将大量虚拟线程调度到少量载体线程上。遇到支持的阻塞 I/O 时可卸载,载体线程继续执行其他任务,适合高并发、I/O 密集型场景。
  • Go Goroutine:Go 运行时调度的轻量级协程;常配合 channel 通信。
go func() {
    fmt.Println("Hello from Goroutine")
}()

线程安全的三个核心性质

  • 原子性:操作不可分割,要么全部完成,要么不执行;例如锁、原子类可保证复合操作的原子性。
  • 可见性:一个线程修改共享变量后,其他线程能及时看到结果;volatile、锁均可提供可见性保证。
  • 有序性:程序执行顺序符合预期,避免指令重排序带来错误;volatile 和锁可建立内存屏障与 happens-before 关系。

死锁、活锁、饥饿属于活跃性问题,不是“有序性”的定义。

i++ 是原子操作吗?

不是。它通常包含:读取 i、加 1、写回三个步骤;多线程下可能发生丢失更新。

Java 内存模型(JMM)

JMM 是 Java 规范定义的抽象内存模型,规定多线程如何读写共享变量,并关注可见性、有序性和原子性。

  • 主内存:共享变量的逻辑存储位置。
  • 工作内存:线程使用共享变量副本的抽象概念,实际可映射到寄存器、缓存等硬件层次。

线程本地缓存可减少直接访问 RAM 的开销,但也会带来可见性问题,因此需要 volatilesynchronizedLock 等同步机制。

线程通信方式

Java:共享内存 + 同步机制

线程可通过共享对象通信。一个线程写入共享变量后,另一个线程需要在正确的同步约束下读取到更新后的值。

常见方式:

  • volatile:保证单个变量读写的可见性与一定的有序性;不保证 i++ 这类复合操作原子性。
  • synchronized:互斥访问,同时保证可见性与有序性。
  • wait() / notify() / notifyAll():协调线程执行;调用时必须持有对象监视器。wait() 会释放锁,唤醒后需重新竞争锁。
  • Condition:配合 ReentrantLock 实现更灵活的等待/通知。
  • Exchanger:两个线程在同步点交换数据。
  • 并发容器、阻塞队列、CompletableFuture 等高级工具。

Go:Channel 通信

Go 倡导“不要通过共享内存通信,而要通过通信共享内存”。channel 用于 Goroutine 间安全地传递数据;也可使用 mutex 保护共享状态。

线程的创建与启动

常见任务提交方式:

  1. 继承 Thread
  2. 实现 Runnable
  3. 实现 Callable 并通过 FutureTask 或线程池获取结果;
  4. 实际工程中优先使用线程池或虚拟线程执行器。
  • Runnable 无返回值、不能直接抛受检异常。
  • Callable<V> 有返回值,可抛出异常。

为什么用 start() 而非直接调用 run()

start() 会请求 JVM 创建并调度新线程,再由新线程执行 run();直接调用 run() 只是当前线程的一次普通同步方法调用,不会产生并发。

线程状态

Java Thread.State 包含:

  • NEW:已创建,未启动;
  • RUNNABLE:可运行(含就绪与运行);
  • BLOCKED:等待获取 synchronized 监视器锁;
  • WAITING:无限期等待,如 Object.wait()Thread.join()
  • TIMED_WAITING:限时等待,如 sleep()、带超时的 wait()
  • TERMINATED:执行结束。

上下文切换与守护线程

  • 上下文切换:CPU 保存当前线程状态并恢复另一线程状态的过程。频繁切换会带来调度、缓存失效等开销。
  • 守护线程(Daemon):为用户线程提供后台服务的线程,如垃圾回收相关线程。所有非守护线程结束时,JVM 通常退出,不会等待守护线程完成。