java基础(十六)操作系统(上)
一、用户态和内核态详解
前面也提过用户态以及内核态(java基础五提过进程切换大,线程切换小)
什么是用户态和内核态?
操作系统中的两种运行模式:用户态(User Mode) 和 内核态(Kernel Mode),它们在权限和可执行操作上有显著区别。
内核态(Kernel Mode)
- CPU可以执行所有指令,访问所有硬件资源
- 权限最高,用于操作系统内核的运行
- 底层操作包括:内存管理、进程管理、设备驱动控制、系统调用等
用户态(User Mode)
- CPU只能执行部分指令,无法直接访问硬件资源
- 权限较低,用于运行用户程序
区分用户态和内核态的重要性
- 安全性:防止用户程序直接访问硬件,避免恶意程序对系统资源的破坏
- 稳定性:用户程序崩溃不会导致整个系统崩溃,提高了系统的可靠性
- 隔离性:明确内核与用户程序的边界,便于系统维护和模块化设计
用户态到内核态的切换过程
当用户程序需要执行特权指令或访问受保护资源时,会通过系统调用接口陷入内核态。这个过程包括:
- 用户程序发起系统调用
- 保存当前用户态上下文(寄存器、程序计数器等)
- 切换到内核态执行系统调用处理程序
- 执行完成后恢复用户态上下文
- 返回用户程序继续执行
// Java中的系统调用示例:文件读写操作
import java.io.*;public class YA33SystemCallExample {public static void main(String[] args) {try {// 文件读取操作会涉及用户态到内核态的切换File file = new File("example.txt");FileInputStream fis = new FileInputStream(file);BufferedReader br = new BufferedReader(new InputStreamReader(fis));String line;while ((line = br.readLine()) != null) {System.out.println(line);}br.close();} catch (IOException e) {e.printStackTrace();}}
}
二、进程管理深入解析
进程与线程的区别
特性 | 进程 | 线程 |
---|---|---|
本质 | 资源分配的基本单位 | 任务调度和执行的基本单位 |
开销 | 大(独立内存空间) | 小(共享内存空间) |
稳定性 | 进程间相互独立 | 线程崩溃可能导致进程崩溃 |
内存分配 | 系统分配独立内存 | 共享进程内存 |
包含关系 | 可包含多个线程 | 属于某个进程 |
进程、线程、协程的对比
类型 | 特点 | 适用场景 |
---|---|---|
进程 | 独立内存空间,上下文切换开销大,稳定性高 | 需要高度隔离的任务 |
线程 | 共享进程内存,上下文切换开销小,存在数据竞争问题 | 需要共享数据的并发任务 |
协程 | 用户态轻量级线程,切换开销极小,由用户控制调度 | 高并发I/O密集型应用 |
进程崩溃隔离机制
进程崩溃不会影响其他进程的主要原因:
- 进程隔离性:每个进程有独立的内存空间,崩溃后由操作系统回收
- 进程独立性:进程间不共享关键资源(如文件描述符、网络连接)
- 硬件保护机制:现代CPU提供内存保护功能,防止进程越界访问
进程资源包含内容
- 虚拟内存空间
- 文件描述符表
- 信号处理程序
- 安全上下文(用户ID、组ID等)
- 处理器状态(寄存器值等)
三、线程设计的必要性与实践
为什么需要线程?
以视频播放器为例说明单进程实现的局限性:
单进程实现的问题:
- 读取、解压、播放三个功能串行执行,效率低下
- 若某个操作阻塞(如读取慢),会导致整个播放不流畅
- 无法充分利用多核CPU的优势
多进程实现的挑战:
- 进程间通信复杂,需要额外机制(如管道、共享内存)
- 创建和维护进程开销大,系统资源消耗高
- 上下文切换成本较高
线程的解决方案:
- 线程之间可以并发执行,提高程序响应性
- 线程共享相同的地址空间,通信效率高
- 线程切换开销小,创建速度快
多线程编程的优劣势分析
优势:
- 提高程序效率,充分利用多核CPU
- 改善程序结构,将复杂任务分解为多个并发单元
- 提高资源利用率,减少I/O等待时间
劣势:
- 存在数据竞争,需使用同步机制(锁、信号量等)
- 调试和测试难度增加,可能出现难以复现的问题
- 线程崩溃可能导致整个进程崩溃
// 多线程示例:使用线程池执行并发任务
import java.util.concurrent.*;public class YA33ThreadExample {public static void main(String[] args) {// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(3);// 提交多个任务for (int i = 0; i < 5; i++) {final int taskId = i;executor.submit(() -> {System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务执行时间} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池executor.shutdown();}
}
线程数量优化策略
多线程并非越多越好,合理设置线程数量的策略:
- CPU密集型任务:线程数 ≈ CPU核心数
- I/O密集型任务:线程数 ≈ CPU核心数 * (1 + 平均等待时间/平均计算时间)
- 混合型任务:根据实际情况调整,监控系统负载动态调整
四、进程与线程切换机制
进程切换详细过程
- 保存当前进程的上下文(寄存器、程序计数器等)
- 更新进程控制块(PCB)状态信息
- 将当前进程PCB移入相应队列(就绪、阻塞等)
- 选择下一个要执行的进程
- 恢复新进程的上下文信息
- 更新内存管理单元(MMU)寄存器
- 跳转到新进程继续执行
线程切换的优势与实现
线程切换比进程切换快的主要原因:
- 不需要切换内存映射表(共享地址空间)
- 只需保存和恢复少量寄存器状态
- 缓存命中率更高(共享相同的内存空间)
线程上下文保存位置:
- 线程控制块(TCB)中保存寄存器状态
- 用户栈或内核栈中保存局部变量和返回地址
- 程序计数器指示下一条指令地址
六、进程状态管理与转换
五种进程状态模型
状态转换详解
- 新建 → 就绪:进程创建完成,等待调度执行
- 就绪 → 运行:被调度器选中,分配CPU时间片
- 运行 → 就绪:时间片用完或更高优先级进程就绪
- 运行 → 阻塞:等待I/O操作或某些事件完成
- 阻塞 → 就绪:等待的事件已完成,可继续执行
- 运行 → 终止:进程执行完成或发生错误被终止
进程控制块(PCB)
PCB是操作系统管理进程的关键数据结构,包含:
- 进程标识信息(PID、父进程PID等)
- 处理器状态信息(寄存器、程序计数器等)
- 进程控制信息(状态、优先级、调度参数等)
- 内存管理信息(页表、段表等)
- 文件管理信息(打开文件列表、工作目录等)
进程间通信(IPC)机制全面解析
IPC方式对比分析
方式 | 原理 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
匿名管道 | 内核缓冲区字节流 | 简单易用 | 只能父子进程间通信 | 顺序字节流处理 |
命名管道 | 文件系统特殊文件 | 无关进程可通信 | 仍然基于字节流 | 客户端-服务器通信 |
消息队列 | 内核维护的消息链表 | 支持消息类型 | 需要内核-用户空间拷贝 | 结构化数据交换 |
共享内存 | 映射相同物理内存 | 速度最快 | 需要同步机制 | 大数据量高速交换 |
信号量 | 计数器控制访问 | 灵活同步机制 | 使用复杂 | 资源访问控制 |
信号 | 异步事件通知 | 简单异步通信 | 信息量有限 | 事件通知处理 |
Socket | 网络接口抽象 | 跨机器通信 | 开销较大 | 分布式系统通信 |
共享内存实现原理
共享内存的实现机制:
- 进程请求共享内存段
- 系统分配虚拟地址空间区域
- 映射到相同的物理内存页
- 进程可直接读写该内存区域
- 需要同步机制(如信号量)协调访问
// Java中使用共享内存的示例(通过MappedByteBuffer)
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;public class YA33SharedMemoryExample {public static void main(String[] args) throws Exception {RandomAccessFile file = new RandomAccessFile("shared memory.bin", "rw");FileChannel channel = file.getChannel();// 创建共享内存映射MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);// 写入数据buffer.putChar('A');buffer.putInt(123);// 读取数据buffer.flip();System.out.println("Char: " + buffer.getChar());System.out.println("Int: " + buffer.getInt());channel.close();file.close();}
}
七、线程同步与通信机制
线程同步原语
1. 互斥锁(Mutex)
保证同一时间只有一个线程可以访问共享资源,防止数据竞争。
2. 条件变量(Condition Variable)
允许线程在某个条件成立时被唤醒,常与互斥锁配合使用。
3. 信号量(Semaphore)
控制对共享资源的访问数量,可用于限流和同步。
4. 读写锁(ReadWrite Lock)
允许多个读操作并发执行,但写操作独占访问。
5. 自旋锁(Spinlock)
忙等待锁,适用于临界区小、持有时间短的场景。
// Java中的线程同步示例
import java.util.concurrent.locks.*;public class YA33SynchronizationExample {private final ReentrantLock lock = new ReentrantLock();private final Condition condition = lock.newCondition();private int sharedData = 0;public void producer() {lock.lock();try {while (sharedData != 0) {condition.await();}sharedData = 1;System.out.println("Produced: " + sharedData);condition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void consumer() {lock.lock();try {while (sharedData == 0) {condition.await();}System.out.println("Consumed: " + sharedData);sharedData = 0;condition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}
线程间通信模式
- 共享内存:通过共享变量进行通信,需要同步机制
- 消息传递:通过队列等结构传递消息,解耦发送者和接收者
- 管道通信:类似进程间管道,用于线程间字节流通信
- Future模式:异步获取执行结果,提高响应性
进程调度算法深度分析
常见调度算法对比
算法 | 原理 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
先来先服务(FCFS) | 按到达顺序调度 | 实现简单 | 平均等待时间长 | 批处理系统 |
最短作业优先(SJF) | 优先调度短作业 | 平均等待时间短 | 可能长作业饥饿 | 知道运行时间的场景 |
优先级调度 | 按优先级调度 | 灵活区分任务重要性 | 可能低优先级饥饿 | 实时系统 |
时间片轮转(RR) | 固定时间片轮流调度 | 公平响应性好 | 上下文切换开销大 | 分时系统 |
多级反馈队列 | 多队列不同优先级和时间片 | 兼顾各种类型进程 | 配置复杂 | 通用操作系统 |
多级反馈队列调度算法详解
多级反馈队列(MFQ)调度算法的工作流程:
- 设置多个优先级队列,优先级从高到低
- 新进程进入最高优先级队列
- 每个队列分配不同的时间片,优先级越高时间片越短
- 进程用完时间片未完成则降级到下一队列
- 只有高优先级队列为空时才调度低优先级队列
- 高优先级新进程到达可抢占当前运行进程
MFQ参数配置策略:
- 队列数量:通常3-5个队列
- 时间片设置:优先级越高时间片越短(如:8ms, 16ms, 32ms, …)
- 调度策略:前n级队列采用RR,最后一级采用FCFS
- 优先级调整:可考虑根据等待时间动态提升进程优先级