enyang
enyang
Published on 2025-12-05 / 1 Visits
0
0

Java多线程<1>——多线程基础

1 什么是进程与线程

1.1 进程(Process)

1.1.1 进程的定义

进程是操作系统进行资源分配的基本单位。当一个程序被启动后,操作系统会为这个程序分配独立的系统资源,这些资源包括内存空间、文件句柄、网络端口等。

例如:运行一段 Java 程序时,启动 Java 虚拟机(JVM),JVM 就是一个操作系统中的进程。

1.1.2 进程的内存结构

一个进程拥有自己独立的内存空间,通常包括以下区域:

  1. 代码区(Code Segment):存放程序的字节码或机器指令。

  2. 数据区(Data Segment):存放静态变量、全局变量。

  3. 堆(Heap):存放动态分配的对象,例如 Java 中 new 出的对象。

  4. 栈(Stack):每个线程一个独立栈,存放方法调用、局部变量、返回地址等。

  5. 系统资源:文件描述符、网络连接、I/O 管道等。

由于这些资源是独立的,一个进程的崩溃通常不会影响到另一个进程,从而增强了系统稳定性。

1.1.3 进程的特点

  1. 彼此独立,拥有独立的内存空间。

  2. 创建、销毁开销较大。

  3. 通信复杂,一般使用 IPC(管道、共享内存、消息队列、套接字)。

1.2 线程(Thread)

1.2.1 线程的定义

线程是程序执行的最小单位,是 CPU 调度的基本单位。一个进程内部可以包含多个线程,多线程共享进程的资源,使程序可以并发执行多个任务。

1.2.2 Java 多线程共享与独立的资源

同进程内多个线程共享:

  1. 堆:对象实例统一放在堆中,共享访问。

  2. 方法区 / 元空间:类信息、常量池等被所有线程共享。

线程独有:

  1. 栈:每个线程独立的调用栈,彼此互不影响。

  2. 程序计数器 PC:用于指示当前执行的位置。

  3. 寄存器:由 CPU 控制。

1.2.3 使用多线程的原因

  1. 利用多核 CPU 并行执行能力。

  2. 当某个线程阻塞(例如 I/O)时,不会影响其他线程继续执行。

  3. 能构建更加流畅、高性能的应用程序。

1.3 进程与线程的区别总结

对比项

进程

线程

内存空间

独立

与同进程线程共享

调度单位

是(CPU 调度线程)

开销

创建速度

销毁成本

通信方式

IPC 复杂

共享内存最简单

关键:Java 多线程的本质在于 JVM 进程内创建多个线程,共享同一堆内存,提高程序并发能力。

2 Java 中的线程模型

2.1 JVM 线程与操作系统线程的关系

Java 使用的是 1:1 线程模型(HotSpot VM)。

  1. 每启动一个 Java Thread,会在底层创建一个对应的操作系统原生线程。

  2. 线程的调度、创建、挂起、恢复由系统执行,而不是 JVM 自己调度。

  3. 每个线程的栈、寄存器等资源由操作系统提供。

2.1.1 这种模型的优点

  1. 可以最大程度利用操作系统的成熟调度算法。

  2. 多核 CPU 下有更强的并行能力。

  3. 性能上限高。

2.1.2 这种模型的缺点

  1. 创建线程成本较高,因为需要调用系统 API 创建原生线程。

  2. 大量线程会造成过多的上下文切换,影响性能。

  3. 无法无限创建线程,因此实际应用中广泛使用线程池。

3 线程的生命周期(Thread Life Cycle)

Java 使用 Thread.State 表示线程状态,共 6 种。

状态

含义

NEW

创建但未启动

RUNNABLE

可运行或正在运行

BLOCKED

被 synchronized 锁阻塞

WAITING

无限等待

TIMED_WAITING

有时限等待

TERMINATED

执行结束

3.1 生命周期状态图

NEW → RUNNABLE → TERMINATED
            ↓            ↑
         WAITING ←→ TIMED_WAITING
            ↓
         BLOCKED

3.2 各状态的进入原因

3.2.1 WAITING(无限等待)

进入原因:

  1. Object.wait()

  2. Thread.join()

  3. LockSupport.park()

特点:

  1. wait 会释放锁

  2. 线程一直等待,直到被 notify、unpark、join 结束等方式唤醒

3.2.2 TIMED_WAITING(定时等待)

进入原因:

  1. Thread.sleep(ms)

  2. Object.wait(ms)

  3. Thread.join(ms)

  4. LockSupport.parkNanos()

特点:

  1. 自动唤醒,不需要其他线程介入

  2. 不会释放锁(sleep 的情况)

3.2.3 BLOCKED(锁阻塞)

进入原因:

  1. 线程尝试进入 synchronized 修饰的代码块/方法

  2. 锁被其他线程持有

特点:

  1. 不释放 CPU

  2. 等待锁释放后自动进入 RUNNABLE 状态

4 创建线程的三种方式

4.1 方式一:继承 Thread 类

示例:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行: " + Thread.currentThread().getName());
    }
}

public class Demo {
    public static void main(String[] args) {
        new MyThread().start();
    }
}

4.1.1 特点

  1. 简单易用。

  2. 受限于 Java 单继承机制,不推荐在大型工程中使用。

4.1.2 常见误区:run() 和 start() 的区别

run() 是普通方法调用
start() 才能创建新线程并让 JVM 调度执行 run()

4.2 方式二:实现 Runnable 接口(最常用)

示例:

class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("执行任务");
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread(new MyTask());
        t.start();
    }
}

4.2.1 优势

  1. 避免单继承限制。

  2. 将任务与线程对象解耦,更适合线程池使用。

  3. 可实现多个任务共享同一个 Runnable 对象。

4.3 方式三:Callable + FutureTask(可返回结果)

示例:

Callable<Integer> callable = () -> {
    return 123;
};

FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();

System.out.println("结果:" + futureTask.get());

4.3.1 特点

  1. call() 方法可以返回值。

  2. 可以抛出异常。

  3. futureTask.get() 是阻塞的,用于等待返回结果。

  4. 常用于多线程计算场景,如分治任务。

5 Thread 常用方法详解

5.1 start()

  1. 启动一个新的线程。

  2. 不能重复调用,否则抛异常 IllegalThreadStateException。

  3. JVM 会调用操作系统 API 创建原生线程。

5.2 run()

  1. 普通方法。

  2. 不能创建新线程。

5.3 sleep()

  1. 暂停当前线程指定时间。

  2. 不释放锁。

  3. 常用于模拟延迟或者控制节奏。

示例:

Thread.sleep(1000);

5.4 join()

让当前线程等待另一个线程执行完。

示例:

t.join();

常用于主线程等待子线程结束。

5.5 yield()

  1. 当前线程提示调度器让出 CPU。

  2. 可能没有效果,实际使用较少。

5.6 interrupt()

  1. 不是强制中断线程。

  2. 是给线程设置一个中断标记位 = true。

  3. 线程需要自己检查标记,决定是否退出。

示例:

while (!Thread.currentThread().isInterrupted()) {
    // 执行任务
}

6 用户线程与守护线程

6.1 守护线程

典型示例:垃圾回收线程(GC)

设置方式:

Thread t = new Thread(task);
t.setDaemon(true);
t.start();

特点:

  1. 守护线程是辅助性质的。

  2. 如果所有用户线程结束,JVM 会自动退出,无论守护线程跑没跑完。


7 线程优先级

7.1 设置方式

thread.setPriority(Thread.MAX_PRIORITY); // 10

范围:1 ~ 10

说明:

  1. 优先级只是调度建议。

  2. 不保证高优先级的线程一定先运行。


8 本章总结

  1. Java 线程底层是操作系统线程,使用 1:1 映射模型。

  2. 线程共享堆和方法区,但各有自己的栈。

  3. 线程有 6 种状态,RUNNABLE/WAITING/BLOCKED 是常见关键状态。

  4. 创建线程的推荐方式是 Runnable 和 Callable。

  5. start() 才能真正启动线程。

  6. sleep() 不释放锁,而 wait() 会释放锁。

  7. interrupt() 是协作中断机制,不会强制终止线程。

  8. 守护线程不会阻止 JVM 退出。


Comment