对于线程产生理解

在Java中,对线程,产生足够多层次

足够多信息量的积累,才可以去理解线程。

我们需要对线程,产生那些领域,深刻全面的认识。才算理解线程呢。

1.线程的原理

2.线程的基本状态

3.在代码层面,使用线程,应该掌握的信息量

1.线程的原理

是**"线程为什么能工作"**------这正好触及了计算机科学最核心的抽象之一。

我来分三层解释:硬件基础 → 操作系统机制 → JVM封装,最后解释为什么它符合你"想象中的效果"。


一、硬件基础:CPU如何"同时"做多件事

1. 物理并行(真正的同时)

多核CPU的每个核心有独立的:

  • ALU(算术逻辑单元)
  • 寄存器组(通用寄存器、PC程序计数器、栈指针)
  • L1/L2缓存

所以双核CPU真正能同时执行两个指令流。

2. 时间分片(宏观并行,微观串行)

单核CPU通过时钟中断来实现"伪并行":

  • CPU每毫秒产生一次时钟中断
  • 操作系统在中断处理函数中,可以强制切换当前执行的指令流

这就是你想象中的效果:多个任务看起来在同时推进


二、操作系统机制:从CPU指令到线程的抽象

核心数据结构:TCB(线程控制块)

操作系统为每个线程维护这样一个结构(简化版):

c 复制代码
struct thread_control_block {
    // 寄存器现场(保存/恢复用)
    uint64_t rax, rbx, rcx, rdx;  // 通用寄存器
    uint64_t rsp;                   // 栈指针
    uint64_t rip;                   // 指令指针(PC)
    
    // 线程标识
    int thread_id;
    int process_id;
    
    // 调度信息
    int priority;
    int state;  // RUNNING, READY, BLOCKED
    
    // 资源引用
    void* kernel_stack;
    void* user_stack;
    struct mm_struct* memory_map;
};

上下文切换(Context Switch)的核心操作

当操作系统决定从线程A切换到线程B时:

assembly 复制代码
; 伪汇编:保存线程A的现场
save_context:
    mov [A_tcb.rax], rax
    mov [A_tcb.rbx], rbx
    mov [A_tcb.rcx], rcx
    mov [A_tcb.rsp], rsp
    mov [A_tcb.rip], rcx    ; 返回地址
    
; 切换到线程B的地址空间(如果属于不同进程)
    mov cr3, [B_tcb.page_table]
    
; 恢复线程B的现场
    mov rax, [B_tcb.rax]
    mov rbx, [B_tcb.rbx]
    mov rcx, [B_tcb.rcx]
    mov rsp, [B_tcb.rsp]
    jmp [B_tcb.rip]          ; 跳转到B上次停下的位置

关键点 :整个切换过程只耗时几十纳秒到几微秒,你完全感知不到。


三、JVM如何封装OS线程

Java线程的1:1映射模型

java 复制代码
Thread t = new Thread(() -> {
    System.out.println("Hello from thread");
});
t.start();

JVM内部实际调用(简化流程):

cpp 复制代码
// OpenJDK 源码简化版
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
    native_thread = new JavaThread(&thread_entry, stack_size);
    os::create_thread(native_thread, THREAD_NORM_PRIORITY);
    os::start_thread(native_thread);
JVM_END

// Linux实现
bool os::create_thread(Thread* thread, int priority) {
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, thread->stack_size());
    int ret = pthread_create(&thread->pd()->pthread, &attr, 
                             thread_native_entry, thread);
    return ret == 0;
}

结论 :Java的Thread对象就是OS原生线程(pthread)的一个包装。


四、为什么能实现"想象中的效果"

你想象中的线程效果应该是:

  1. 多个任务能同时运行
  2. 一个任务阻塞(如等IO),不影响其他任务
  3. 它们共享内存,但又有独立的状态

这背后的实现逻辑:

1. 并发(Concurrency)vs 并行(Parallelism)

  • 并行:多核CPU真正同时执行(物理现实)
  • 并发:单核通过时间分片制造的幻觉(你想象中的效果)

操作系统通过调度算法(CFS、RR等)让你感知不到切换。

2. 阻塞不占CPU的关键:线程状态迁移

当你执行read()系统调用:

java 复制代码
socket.read(buffer);  // 阻塞等待网络数据

OS内部:

  1. 线程主动放弃CPU,状态从RUNNINGBLOCKED
  2. 将线程从运行队列移到等待队列(关联到socket的事件)
  3. 调度器选择另一个READY线程运行
  4. 网卡数据到达时,硬件触发中断,OS将线程移回READY队列

关键 :阻塞的线程完全不消耗CPU时间片

3. 共享内存的实现:虚拟地址空间

不同线程属于同一进程,共享页表:

复制代码
虚拟地址空间(每个线程看到一样的):
0x00000000 ┌─────────────┐
           │   text段    │ ← 代码(共享)
           ├─────────────┤
           │   data段    │ ← 全局变量(共享)
           ├─────────────┤
0x7fffffff │   heap      │ ← 堆(共享,new出来的对象)
           └─────────────┘
           
但是每个线程有独立的:
- 栈(虚拟地址不同范围)
- 寄存器(物理上独立)

所以:两个线程可以同时访问同一个堆对象(需要同步),但局部变量互不干扰


五、一个具体的底层示例

用Linux的clone()系统调用直接创建线程(绕过Java):

c 复制代码
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <sys/wait.h>

int thread_func(void* arg) {
    printf("Thread running with PID: %d\n", getpid());
    return 0;
}

int main() {
    char stack[1024 * 1024];  // 1MB栈空间
    
    // CLONE_VM: 共享内存空间(关键!)
    // CLONE_SIGHAND: 共享信号处理器
    // CLONE_FILES: 共享文件描述符
    int tid = clone(thread_func, stack + sizeof(stack),
                    CLONE_VM | CLONE_SIGHAND | CLONE_FILES | SIGCHLD,
                    NULL);
    
    waitpid(tid, NULL, 0);
    return 0;
}

这就是线程的本质clone()时指定CLONE_VM标志,让多个执行流共享同一个地址空间。


总结:三层抽象

层次 实现机制 作用
硬件 多核 + 时间中断 提供同时/分时执行的能力
操作系统 TCB + 上下文切换 + 调度算法 管理线程生命周期,制造并发幻觉
JVM 1:1映射到pthread + Java对象封装 让Java代码能安全地使用OS线程

回答你的核心问题

为什么能实现想象中的线程效果?

因为:

  1. 硬件提供了真正的并行能力 (多核)和分时机制(时钟中断)
  2. 操作系统封装了上下文切换,让你感知不到切花费的时间(微秒级)
  3. 共享内存加独立栈的设计,既满足协作又满足隔离

你想象中的"多个任务同时运行、一个阻塞不影响其他",本质上是操作系统通过中断、队列、调度算法共同制造的优雅幻觉。


2.线程的基本状态

Java线程生命周期与状态:完整深度解析

让我从底层原理 → JVM实现 → 代码验证三个层面,给你彻底讲透线程状态。


一、六种状态总览(Thread.State枚举)

java 复制代码
public enum State {
    NEW,           // 新生,尚未启动
    RUNNABLE,      // 可运行(可能正在执行,也可能在等待CPU)
    BLOCKED,       // 阻塞(等待获取锁)
    WAITING,       // 等待(无限期等待被唤醒)
    TIMED_WAITING, // 限时等待(有时间期限的等待)
    TERMINATED     // 终止(执行完毕)
}

核心区别图

复制代码
NEW → RUNNABLE → TERMINATED
         ↓
    (进入同步块时)
         ↓
    BLOCKED ←→ RUNNABLE
         ↓
    (调用wait/park/join)
         ↓
    WAITING / TIMED_WAITING

二、每个状态的深度剖析

1. NEW(新生)

触发条件

java 复制代码
Thread t = new Thread(() -> {});
// 此时t处于NEW状态

底层特征

  • JVM已分配Thread对象,分配了栈内存(通常1MB)
  • 没有关联操作系统线程(没有pthread_create)
  • TCB(线程控制块)尚未创建

验证方法

java 复制代码
Thread t = new Thread(() -> {});
System.out.println(t.getState()); // NEW
System.out.println(t.isAlive());   // false

2. RUNNABLE(可运行)

触发条件

java 复制代码
t.start();  // NEW → RUNNABLE

底层真相:RUNNABLE实际对应OS的两种状态

c 复制代码
// Linux内核中的线程状态
enum task_state {
    TASK_RUNNING,     // 正在运行或就绪(Java的RUNNABLE)
    TASK_INTERRUPTIBLE,  // 阻塞IO(Java也标记为RUNNABLE!)
    TASK_UNINTERRUPTIBLE,
    TASK_STOPPED,
    TASK_ZOMBIE
};

重要:Java的RUNNABLE ≠ CPU正在运行,而是**"在JVM看来可以运行"**,包括:

  • 正在CPU执行
  • 在就绪队列等待CPU时间片
  • 正在执行系统调用(如socket.read()) ← 这个最容易被误解

验证例子

java 复制代码
Thread t = new Thread(() -> {
    try {
        Thread.sleep(10000); // 调用OS的nanosleep系统调用
    } catch (InterruptedException e) {}
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // TIMED_WAITING,不是RUNNABLE

3. BLOCKED(阻塞)

触发条件:等待synchronized锁时

java 复制代码
public class BlockedDemo {
    private static final Object lock = new Object();
    
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                while (true) {}  // 死循环持有锁
            }
        });
        
        Thread t2 = new Thread(() -> {
            synchronized (lock) {  // t2在这里阻塞
                System.out.println("不会被打印");
            }
        });
        
        t1.start();
        Thread.sleep(100);  // 确保t1先获得锁
        t2.start();
        Thread.sleep(100);
        System.out.println(t2.getState()); // BLOCKED
    }
}

底层实现

java 复制代码
// JVM内部,synchronized对应monitorenter/monitorexit字节码
// 当锁被持有时,线程进入ObjectMonitor的_WaitSet或_EntryList

BLOCKED vs WAITING区别

  • BLOCKED:等待其他线程释放锁(被动)
  • WAITING:主动调用wait/park等待被其他线程唤醒(主动)

4. WAITING(无限期等待)

触发方式(3种):

java 复制代码
// 1. Object.wait()
synchronized (lock) {
    lock.wait();  // 必须持有锁,释放锁并等待
}

// 2. Thread.join()
thread.join();  // 等待thread执行完毕

// 3. LockSupport.park()
LockSupport.park();  // 最底层实现,不需要锁

底层机制

java 复制代码
// Object.wait() 的底层实现(简化)
// 1. 释放synchronized锁
// 2. 线程进入ObjectMonitor的_WaitSet
// 3. 状态设置为WAITING
// 4. 调用os::sleep()让出CPU

完整示例

java 复制代码
Thread waiter = new Thread(() -> {
    synchronized (lock) {
        try {
            System.out.println("waiter: 开始等待");
            lock.wait();  // 释放锁,进入WAITING
            System.out.println("waiter: 被唤醒");
        } catch (InterruptedException e) {}
    }
});

Thread notifier = new Thread(() -> {
    synchronized (lock) {
        System.out.println("notifier: 准备唤醒");
        lock.notify();  // 唤醒waiter
        // 但notifier此时还没释放锁!
        Thread.sleep(1000);
    }
});

waiter.start();
Thread.sleep(100);
waiter.start();
System.out.println(waiter.getState()); // WAITING

5. TIMED_WAITING(限时等待)

触发方式(5种常见):

java 复制代码
// 1. Thread.sleep(long millis)
Thread.sleep(1000);

// 2. Object.wait(long timeout)
synchronized (lock) {
    lock.wait(1000);
}

// 3. Thread.join(long millis)
thread.join(1000);

// 4. LockSupport.parkNanos(long nanos)
LockSupport.parkNanos(1_000_000_000L);

// 5. LockSupport.parkUntil(long deadline)
LockSupport.parkUntil(System.currentTimeMillis() + 1000);

底层差异

c 复制代码
// Linux内核中
// sleep/wait带超时 → 使用高精度定时器(hrtimer)
// 超时后由内核触发中断唤醒线程

与WAITING的唯一区别 :有自动唤醒机制,超时后自动回到RUNNABLE。


6. TERMINATED(终止)

触发条件

java 复制代码
// 1. run()方法正常返回
thread.run();  // 执行完毕

// 2. 未捕获的异常抛出
thread.run();  // 抛出异常退出

// 3. 调用stop()(已废弃,不安全)
thread.stop();  // 不推荐

底层清理

  • 释放持有的所有锁(通过monitorexit)
  • 线程栈内存被回收(但Thread对象仍在堆中)
  • OS线程被销毁(pthread_join或detach)

验证

java 复制代码
Thread t = new Thread(() -> {});
t.start();
t.join();
System.out.println(t.getState()); // TERMINATED
System.out.println(t.isAlive());   // false
// 仍然可以访问t的name、priority等属性

三、状态转换的完整路径图

复制代码
                    ┌─────────────────────────────────────┐
                    │                                     │
                    │  ╔═══╗                              │
                    ▼  ║NEW║                              │
           t.start() ╚═══╝                              │
                    │                                     │
                    ▼                                     │
              ╔════════════╗                              │
              ║ RUNNABLE   ║                              │
              ╚════════════╝                              │
                    │                                     │
         ┌──────────┼──────────┬──────────────┐          │
         │          │          │              │          │
         ▼          ▼          ▼              ▼          │
   获取锁失败   sleep/wait   wait/park    run()结束      │
     /IO阻塞    /join带超时  /无超时                      │
         │          │          │              │          │
         ▼          ▼          ▼              ▼          │
    ╔═══════╗  ╔════════════╗ ╔═══════╗   ╔══════════╗  │
    ║BLOCKED║  ║TIMED_      ║ ║WAITING║   ║TERMINATED║  │
    ╚═══════╝  ║WAITING     ║ ╚═══════╝   ╚══════════╝  │
              ╚════════════╝     │              │        │
                    │            │              │        │
              超时/唤醒     被notify/      (终态)         │
              锁可用        /interrupt                     │
                    │            │                         │
                    └────────────┘                         │
                         │                                 │
                         └─────────────────────────────────┘

四、容易混淆的关键场景

场景1:Thread.sleep() vs Object.wait()

java 复制代码
// sleep:不释放锁
synchronized (lock) {
    Thread.sleep(1000);  // 其他线程无法进入此同步块
}

// wait:释放锁
synchronized (lock) {
    lock.wait(1000);     // 锁被释放,其他线程可以进入
}

场景2:BLOCKED vs WAITING 的本质区别

java 复制代码
// BLOCKED: 争夺锁失败
synchronized (lock) {  // 如果lock被持有,进入BLOCKED
}

// WAITING: 已经获得锁,主动放弃
synchronized (lock) {
    lock.wait();  // 已持有锁,主动进入WAITING
}

场景3:IO操作的状态谜团

java 复制代码
// 传统IO(BIO):在JVM看来是RUNNABLE
socket.read();  // 底层调用read系统调用,线程阻塞在OS
// JVM不知道OS在阻塞,所以状态仍是RUNNABLE

// NIO(非阻塞)
socketChannel.read(buffer);  // 立即返回,不会阻塞
// 通过Selector管理多路复用

五、实战:如何监控和调试状态

1. 使用jstack查看线程状态

bash 复制代码
jstack <pid>

输出示例:

复制代码
"pool-1-thread-1" #11 prio=5 os_prio=0 tid=0x00007f... nid=0x3e2c waiting on condition [0x00007f...]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)

2. 编程式监控

java 复制代码
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadMXBean.getAllThreadIds();
for (long id : threadIds) {
    ThreadInfo info = threadMXBean.getThreadInfo(id);
    System.out.printf("Thread %d: %s\n", id, info.getThreadState());
}

3. 死锁检测

java 复制代码
// 自动检测死锁
long[] deadlockedIds = threadMXBean.findDeadlockedThreads();
if (deadlockedIds != null) {
    System.out.println("Deadlock detected!");
    for (long id : deadlockedIds) {
        ThreadInfo info = threadMXBean.getThreadInfo(id);
        System.out.println(info);
    }
}

六、常见面试陷阱题

题1:以下代码输出什么?

java 复制代码
Thread t = new Thread(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
});
System.out.println(t.getState());  // NEW
t.start();
System.out.println(t.getState());  // RUNNABLE(可能输出RUNNABLE或TIMED_WAITING?)
Thread.sleep(500);
System.out.println(t.getState());  // TIMED_WAITING(因为sleep还没结束)

题2:synchronized代码块内调用wait()后,线程处于什么状态?

  • 答案:WAITING(释放了锁)

题3:线程在socket.read()阻塞时,Java层面是什么状态?

  • 答案:RUNNABLE(因为JVM无法感知OS的阻塞)

七、性能影响与调优建议

状态 CPU消耗 内存占用 典型问题
RUNNABLE 正常 CPU飙升,上下文切换频繁
BLOCKED 正常 锁竞争激烈,吞吐量下降
WAITING 极低 正常 等待时间过长,响应慢
TIMED_WAITING 极低 正常 超时时间不合理

调优技巧

  1. 减少BLOCKED:使用更细粒度的锁、读写锁、CAS
  2. 避免长时间WAITING:设置合理超时、使用异步回调
  3. 监控线程数:过多的WAITING容易隐藏内存泄漏(ThreadLocal)

如果你需要,我可以继续深入:

  • 具体状态转换的代码示例集
  • 如何通过JNI/Instrumentation观测底层状态
  • 虚拟线程(Virtual Thread)的状态模型差异

3.在代码层面,使用线程,应该掌握的信息量

一、基础API层(必须100%掌握)

1.1 线程创建方式及本质区别

java 复制代码
// 方式1:继承Thread(不推荐,单一继承限制)
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}
// 问题:每次执行都要创建新线程,无法复用

// 方式2:实现Runnable(推荐)
Runnable task = () -> System.out.println("执行");
Thread t = new Thread(task);
t.start();

// 方式3:实现Callable(可返回值+抛异常)
Callable<String> callable = () -> {
    Thread.sleep(1000);
    return "结果";
};
FutureTask<String> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
String result = futureTask.get(); // 阻塞获取

// 方式4:线程池(生产唯一选择)
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(callable);

核心知识点

  • run() vs start():直接调用run()只是在当前线程执行,不会创建新线程
  • 线程只能start()一次,第二次会抛IllegalThreadStateException
  • Runnable vs Callable:Runnable不能返回值也不能抛受检异常

1.2 线程中断机制(最难理解的部分)

java 复制代码
// 错误认知:interrupt()不是强制停止线程
Thread t = new Thread(() -> {
    while (true) {
        // 需要手动检查中断标志
        if (Thread.currentThread().isInterrupted()) {
            System.out.println("收到中断信号,清理资源并退出");
            break;
        }
    }
});
t.start();
t.interrupt(); // 设置中断标志为true

// 阻塞方法的中断响应
Thread t2 = new Thread(() -> {
    try {
        Thread.sleep(10000); // sleep、wait、join都会响应中断
    } catch (InterruptedException e) {
        // 注意:抛出异常后,中断标志会被清除!
        System.out.println("中断标志:" + Thread.currentThread().isInterrupted()); // false
        // 正确做法:重新设置中断标志或退出
        Thread.currentThread().interrupt(); // 恢复中断标志
        return;
    }
});

必须掌握的3个方法

java 复制代码
// 1. 静态方法,返回当前线程的中断标志,不清除标志
Thread.interrupted()  // 注意:会清除中断标志!

// 2. 实例方法,返回中断标志,不清除
thread.isInterrupted()

// 3. 设置中断标志
thread.interrupt()

1.3 join、yield、sleep的精确语义

java 复制代码
// join:等待线程结束(底层使用wait实现)
Thread t = new Thread(() -> {
    Thread.sleep(1000);
});
t.start();
t.join();      // 当前线程阻塞,等待t执行完
t.join(500);   // 最多等待500ms

// yield:让出CPU(仅仅是建议,不保证生效)
Thread.yield(); // 从RUNNING→RUNNABLE,让出时间片

// sleep:休眠(不释放锁!)
synchronized (lock) {
    Thread.sleep(1000); // 持有锁进入TIMED_WAITING
    // 其他线程无法获取lock
}
相关推荐
唐青枫12 小时前
Java Flyway 实战指南:用 SQL 脚本管理数据库版本
java
huangdong_19 小时前
电商平台图片URL原图转换技术深度解析:从缩略图到高清原图的完整方案
java·后端·spring
記億揺晃着的那天19 小时前
Java 调用外部 Go 程序的实践:ProcessBuilder 在生产环境中的应用
java·golang·processbuilder
JAVA面经实录91719 小时前
Java 数据结构与算法 (终极完整学习文档)
java·数据结构·算法
JAVA面经实录91720 小时前
操作系统面试题
java·服务器·数据库·计算机网络·面试
一杯奶茶¥20 小时前
基于springboot的失物招领管理系统带万字文档 校园失物招领管理系统 失物认领管理系统java springboot vue
java·vue.js·spring boot·java项目
不能只会打代码21 小时前
边缘视频分析平台的架构设计与性能优化——从750ms到190ms的调优之路
java·spring boot·redis·性能优化·边缘计算·物联网竞赛
小刘|21 小时前
Spring AI Alibaba 集成和风天气 API 实战
java·服务器·前端
KANGBboy21 小时前
java知识五(继承)
java·开发语言
AI人工智能+电脑小能手21 小时前
【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
java·开发语言·面试