跳至主要內容

EkkoSonya's Blog

好好学习,天天向上

OS5 - 进程管理3

OS

Java 线程状态

Java 没有 Ready 状态,线程状态有 6 种:

NEW              新建
RUNNABLE         可运行(包含 Ready + Running)
BLOCKED          阻塞(等待锁)
WAITING          等待(wait/join/park 等)
TIMED_WAITING    限时等待
TERMINATED       终止

codejavaCS大约 2 分钟
OS5 - 进程管理3

OS

中断切换

以线程 A 发起读取磁盘文件为例

第一阶段:发起 I/O 时的“软中断”与主动切换(线程 A 交出 CPU)

当线程 A 执行到读取文件的代码时,它其实是没有权限直接控制底层硬件的,必须向操作系统老大哥(内核)求助。

  • 系统调用(软中断/Trap): 线程 A 调用 read() 函数,这会触发一个软中断(在 x86 架构下通常是 int 0x80 指令或 syscall 指令)。CPU 收到这个信号后,会从“用户态”切换到“内核态”,开始执行操作系统的代码。
  • 下达指令与阻塞: 操作系统内核接收到请求,把读取任务交给 DMA 和磁盘控制器。因为此时数据还没准备好,操作系统会将线程 A 的状态标记为 BLOCKED(阻塞状态),并把它移出 CPU 的运行队列。
  • 第一次上下文切换(Context Switch): 既然线程 A 被挂起了,CPU 不能闲着。操作系统的调度器(Scheduler)会立刻介入,从就绪队列里挑出一个线程 B,把 CPU 的控制权交给它。
    • 此时,DMA 在默默搬运硬盘数据,而 CPU 正在全速运行线程 B 的代码。两者互不干扰。

codejavaCS大约 14 分钟
Java面试题 - java基础9 (lambda闭包)

Java 基础

我在主线程创建了一个锁对象,然后对应的子线程为什么可以得到这个锁

在主线程中通过 new 关键字(例如 new Object() 或 new ReentrantLock())创建一个锁对象时,这个对象是被分配在 JVM 的**堆(Heap)**内存中的

在 Java 内存模型中,每个线程有自己私有的虚拟机栈(用于存储局部变量表、方法出口等),但堆内存是所有线程共享的。只要子线程能够拿到这个对象的内存地址(引用),它就可以访问这个对象。

传递的是对象引用

当主线程启动子线程时,通常会将这个锁对象的引用(相当于这把锁在堆内存中的真实地址)通过某种方式传递给子线程:


codejava大约 4 分钟
JUC 八股18 - 并发工具类 (HashTable)

并发工具类

HashTable

alt text
  • Hashtable的底层数据结构主要是数组加上链表,数组是主体,链表是解决hash冲突存在的。

  • HashTable是线程安全的,实现方式是Hashtable的所有公共方法均采用synchronized关键字,当一个线程访问同步方法,另一个线程也访问的时候,就会陷入阻塞或者轮询的状态。

原理

本质就是构建了一个存储了 Entry<?,?> 对象的数组,数据就存放在具体的 Entry 对象里,对应的索引根据 hash 来算


codejavajuc八股大约 7 分钟
JUC 八股20 - 并发工具类

并发工具类

作用
Semaphore 限制线程的数量
Exchanger 两个线程交换数据
CountDownLatch 线程等待直到计数器减为 0 时开始工作
CyclicBarrier 作用跟 CountDownLatch 类似,但是可以重复使用
Phaser 增强的 CyclicBarrier

codejavajuc八股大约 5 分钟
JUC 八股20 - 线程池1(阻塞队列)

线程池

阻塞队列

常用的有五种

  • 有界队列 ArrayBlockingQueue;
  • 无界队列 LinkedBlockingQueue;
  • 优先级队列 PriorityBlockingQueue;
  • 延迟队列 DelayQueue;
  • 同步队列 SynchronousQueue
alt text

阻塞队列接口 BlockingQueue<E>


codejavajuc八股大约 2 分钟
JUC 八股21 - 线程池2

线程池

线程池是用来管理和复用线程的工具,它可以减少线程的创建和销毁开销

在 Java 中,ThreadPoolExecutor 是线程池的核心实现,它通过核心线程数、最大线程数、任务队列和拒绝策略来控制线程的创建和执行

alt text

任务执行流程如下:

任务提交 → 核心线程执行 → 任务队列缓存 → 非核心线程执行 → 拒绝策略处理

提交任务 → 核心线程是否已满?
  ├─ 未满 → 创建核心线程执行
  └─ 已满 → 任务入队
      ├─ 队列未满 → 等待执行
      └─ 队列已满 → 创建非核心线程
          ├─ 未达最大线程数 → 执行任务
          └─ 已达最大线程数 → 执行拒绝策略

codejavajuc八股大约 6 分钟
JUC 八股22 - 线程池3

线程池

IO 密集 / CPU 密集

任务类型 线程状态 CPU 使用 多线程效果
CPU 密集型 RUNNABLE 可能更慢(切换开销)
IO 密集型 WAITING 更快(等待时间重叠)

codejavajuc八股大约 6 分钟
JUC 八股23 (Future)

Future类

Future 是为了解决多线程执行任务并获取结果的问题

在传统的同步编程中,我们调用一个方法,必须死死等在这个方法那里,直到它执行完毕返回结果,代码才能继续往下走。

而 Future 代表的是一个异步计算的结果

当你把一个耗时的任务(比如网络请求、复杂计算)扔给线程池(ExecutorService)去后台执行时,线程池会立刻塞给你一张“取餐小票”(也就是一个 Future 对象)。

拿到这张小票后,你的主线程不需要原地死等,可以立刻去干别的活儿。等你需要那个耗时任务的结果时,再拿着这张小票去兑换(调用 get() 方法)


codejavajuc八股大约 5 分钟
JUC 八股24 (实际场景)

实际场景

多线程打印奇偶数,怎么控制打印的顺序

使用 ReentrantLock + Condition

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class OddEvenPrinterLock {
  private int count = 1;
  private final int MAX = 10;
  
  // 主线程创建的锁对象
  private final ReentrantLock lock = new ReentrantLock(); 
  // 创建两个条件变量,相当于两个休息室
  private final Condition oddCondition = lock.newCondition();
  private final Condition evenCondition = lock.newCondition();

  public void printOdd() {
      while (count <= MAX) {
          lock.lock(); // 尝试获取锁
          try {
              while (count % 2 == 0) {
                oddCondition.await(); // 奇数线程去奇数休息室睡觉,并释放锁
              }
              if (count <= MAX) {
                System.out.println(Thread.currentThread().getName() + " 打印奇数: " + count);
                count++;
                evenCondition.signal(); // 明确去偶数休息室叫醒偶数线程
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          } finally {
              lock.unlock(); // 切记在 finally 中释放锁
          }
      }
  }

  public void printEven() {
      while (count <= MAX) {
          lock.lock();
          try {
              while (count % 2 != 0) {
                  evenCondition.await(); // 偶数线程去偶数休息室睡觉,并释放锁
              }
              if (count <= MAX) {
                  System.out.println(Thread.currentThread().getName() + " 打印偶数: " + count);
                  count++;
                  oddCondition.signal(); // 明确去奇数休息室叫醒奇数线程
              }
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          } finally {
              lock.unlock();
          }
      }
  }

  public static void main(String[] args) {
      OddEvenPrinterLock printer = new OddEvenPrinterLock();
      new Thread(printer::printOdd, "Thread-Odd").start();
      new Thread(printer::printEven, "Thread-Even").start();
  }
}

codejavajuc八股大约 2 分钟