JUC并发编程(一)
JUC概述
JUC是什么
JUC 是 Java 中处理多线程编程的一个工具包(java.util.concurrent
包),提供了一套强大的工具,让开发者能更轻松、安全地编写高性能的多线程程序。
进程 线程 协程
进程(Process)
进程是操作系统进行资源分配和调度的基本单位。它是一个正在执行的程序的实例,拥有独立的地址空间、代码、数据和系统资源(如文件句柄、网络端口等)。
可以简单理解为资源+线程,操作系统运行的一个程序。
线程(Thread)
线程是进程内的执行单元,是CPU调度和执行的基本单位。一个进程可以包含多个线程,所有线程共享进程的地址空间和资源。
比如一个程序通过一个个线程对CPU进行调度。
协程(Coroutine / Fiber)
协程是用户态的轻量级线程,由程序员在用户空间管理调度(而非操作系统内核)。协程在同一个线程内执行,通过协作式调度(非抢占式)来切换执行流。
总结
- 先有进程,然后进程可以创建线程,线程是依附在进程里面的,线程里面可以包含多个协程。
- 进程之间不共享资源,所有的线程共享进程的资源,协程共享线程的资源。
串行 并发 并行
串行(Serial)
串行执行是指任务按顺序一个接一个地执行,只有在前一个任务完全完成后,后一个任务才能开始。
1 | 时间轴:|--- 任务A ---||--- 任务B ---||--- 任务C ---|> |
并发(Concurrent)
并发是指在重叠的时间段内处理多个任务,通过任务间的快速切换来模拟”同时”执行的效果。这可以在单核或多核处理器上实现。
多个线程微观上是串行的,但是由于CPU切换速度很快,宏观上这些线程是“同时”执行的。
1 |
|
并行(Parallel)
并行是指真正的同时执行多个任务,多个核心执行不多个进程/线程。
1 | 多核CPU时间轴: |
上下文切换
CPU上下文
多核CPU在运行任务时,使用程序计数器来存储当前执行指令的位置或者下一条将要执行的指令的位置,而CPU寄存器则是CPU中的内存。它们都是CPU在运行时必须依赖的环境,因此也被称作为CPU上下文。
CPU上下文切换
CPU上下文切换就是把前一个任务的上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文开始执行。
而保存起来的上下文,会存储在系统内核中,并在任务重新执行时再次加载进来。
根据CPU执行的任务的不同,上下文切换分为这三种:进程上下文切换、线程上下文切换,中断上下文切换。
什么时候会发生上下文切换
主动让出CPU(自愿性切换 - Voluntary)
线程主动请求暂停,并告诉调度器“我可以被切换了”。
- 等待资源(I/O操作):这是最常见的原因。当一个线程需要读取文件、等待网络数据包、或获取用户输入时,它会发起一个系统调用并进入睡眠(Sleep)或阻塞(Blocked)状态。CPU立即被释放,操作系统会选择另一个就绪(Runnable)的线程(可以是同一进程的,也可以是其他进程的)来运行。
- 主动休眠:线程调用睡眠函数(如
sleep()
,nanosleep()
),明确告诉操作系统“我需要在未来X段时间内放弃CPU”。 - 等待同步原语:线程尝试获取一个已经被其他线程持有的锁(如互斥锁Mutex)、信号量(Semaphore)时,它会被阻塞,从而触发切换。
- 主动让出:线程显式调用类似
pthread_yield()
或sched_yield()
的函数,建议调度器立即切换上下文,让其他同等优先级的线程运行。
被操作系统强制剥夺(非自愿性/抢占式切换 - Involuntary/Preemptive)
线程本身还想继续运行,但操作系统基于某种策略决定中断它。
- 时间片耗尽(Time Slice Exhausted):这是抢占式调度的基石。每个线程被分配一个固定的时间片(Quantum,通常是几毫到几百毫秒)。当线程用完了它的时间片,操作系统会触发一个时钟中断(Timer Interrupt),中断处理程序会检查时间片,如果耗尽,则调用调度器切换到另一个就绪线程。
- 更高优先级线程就绪:如果一个处于睡眠状态的高优先级线程等待的事件发生了(例如它等的I/O完成了),它会立刻变为就绪状态。调度器可能会抢占当前正在运行的低优先级线程,即使其时间片还没用完,也要马上切换到高优先级线程,以保证系统的响应性。
被硬件中断打断
为了响应硬件的各种事件设计出来的,中断程序会打断进程的正常执行。例如,当前CPU正在全力执行一些程序,这个时候我们挪了挪鼠标,按了下键盘。CPU就必须中断正在执行的程序,转而去响应这些硬件的事件。
总结
线程上下切换开销小,进程直接切换开销大。线程切换虽然开销小,但是还是有消耗,不能盲目添加过多的线程。
- 当发生线程间的上下文切换时,如果两个线程属于同一个进程,切换会非常“廉价”。
- 不需要切换内存地址空间(页表)、文件描述符表等资源。只需要切换线程的私有资源(寄存器、栈指针、程序计数器等)。
- 如果两个线程属于不同进程,切换则更“昂贵”。
- 除了切换线程的私有资源,还必须切换整个内存地址空间(通过切换页表寄存器实现),这会导致Translation Lookaside Buffer (TLB) 被刷新,开销更大。
创建线程
继承 Thread 类
1 | public class MyThread extends Thread{ |
实现Runnable接口
1 | public class MyRunnable implements Runnable{ |
实现 Callable 接口
1 | public class MyCallable implements Callable<String> { |
使用线程池
1 | public class MyThreadPool { |
Java Thread 类常用方法
方法名 | 作用描述 | 返回值 | 注意事项 |
---|---|---|---|
start() |
启动线程,使其进入就绪状态 | void |
只能调用一次,第二次会抛 IllegalThreadStateException |
run() |
线程要执行的任务内容 | void |
不要直接调用,应该通过 start() 间接调用 |
sleep(long millis) |
让当前线程休眠指定毫秒数 | void |
静态方法,会抛 InterruptedException |
sleep(long millis, int nanos) |
更精确的休眠(毫秒+纳秒) | void |
静态方法,实际精度依赖操作系统 |
yield() |
提示调度器当前线程愿意让出CPU | void |
静态方法,只是提示,不保证一定暂停 |
join() |
等待该线程终止 | void |
会抛 InterruptedException |
join(long millis) |
最多等待该线程终止指定毫秒数 | void |
超时后继续执行,抛 InterruptedException |
interrupt() |
中断目标线程 | void |
设置中断标志位,非强制终止 |
isInterrupted() |
测试线程是否被中断 | boolean |
实例方法,不清除中断状态 |
static interrupted() |
测试当前线程是否被中断 | boolean |
静态方法,会清除中断状态 |
setPriority(int priority) |
设置线程优先级(1-10) | void |
只是提示,具体取决于JVM和OS实现 |
getPriority() |
获取线程优先级 | int |
默认是5(NORM_PRIORITY ) |
setName(String name) |
设置线程名称 | void |
便于调试和监控 |
getName() |
获取线程名称 | String |
默认格式:"Thread-" + n |
static currentThread() |
获取当前执行线程的引用 | Thread |
非常重要的静态方法 |
getState() |
获取线程状态 | Thread.State |
返回枚举:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED |
isAlive() |
测试线程是否存活 | boolean |
线程启动后且未死亡返回 true |
setDaemon(boolean on) |
设置是否为守护线程 | void |
必须在 start() 前调用,默认为false |
isDaemon() |
测试是否为守护线程 | boolean |
守护线程不会阻止JVM退出 |
stop() |
已废弃:强制停止线程 | void |
不安全,会导致资源未释放,不要使用! |
suspend() |
已废弃:挂起线程 | void |
容易导致死锁,不要使用! |
resume() |
已废弃:恢复挂起的线程 | void |
配套 suspend() ,同样不要使用! |
线程的6种状态
线程状态 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |