## 一、进程和线程的概念 #### 1.进程(Process) **什么是进程?** 你可以把一个**进程**理解为一个**独立的应用程序**。当你启动一个程序(比如打开一个 Word 文档,或者启动一个 Java 程序),操作系统就会为它创建一个进程。 **进程是操作系统进行资源分配和调度的基本单位。** 每个进程都拥有自己独立的一块内存空间、一套系统资源(如文件句柄、网络端口等)。一个进程崩溃后,在保护模式下,一般不会对其他进程产生影响。 **比喻:** 把进程想象成一个**独立的厨房**。这个厨房里有: * **专属的空间**:墙壁把它和别的厨房隔开。 * **专属的资源**:自己的灶台、锅碗瓢盆、食材。 * **独立的工作**:在这个厨房里可以单独做一道菜(比如红烧肉),不会直接干扰到另一个厨房做蛋糕。 #### 2. 线程(Thread) **什么是线程?** **线程**是**进程中的一个执行单元**,也被称为“轻量级进程”。一个进程可以包含多个线程,所有线程**共享进程的内存空间和资源**。 **线程是 CPU 调度和执行的基本单位。** 每个线程有自己独立的执行路径、程序计数器、栈和局部变量,但它们共享堆内存和全局变量。 **比喻:** 继续用厨房的比喻,把一个进程(厨房)里的**线程**想象成这个厨房里的**多个厨师**。 * **共享空间和资源**:所有厨师都在同一个厨房里工作,共享同一个灶台、冰箱、锅具。 * **分工协作**:一个厨师专门切菜,一个厨师专门炒菜,一个厨师专门装盘。他们协作完成一顿大餐。 * **需要协调**:如果两个厨师同时抢一把菜刀(共享资源),就会发生冲突。所以需要有“协调机制”(比如锁),来保证一个时间只有一个人能用菜刀。 ## 二、实现多线程的方式 #### 1、继承Thread类 多线程的启动是调用对象的`start()`方法,重写函数`run()` ```java public class DemoThread extends Thread { @Override public void run() { for (int i = 0; i < 20000; i++) { System.out.println(i); } } } ``` 示例代码 ```java import DemoThread; public class TestDemoThread { public static void main(String[] args) { DemoThread test1 = new DemoThread(); test1.start(); for (int i = 0; i < 20000; i++) { System.out.println("main:" + i); } } } ``` #### 2、实现Runnable接口 新建类并实现`Runnable`接口,重写函数`run()` ```java public class DemoRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 20000; i++) { System.out.println(i); } } } ``` 示例代码 ```java import DemoRunnable; public class TestDemoRunnable { public static void main(String[] args) { Thread t = new Thread(new DemoRunnable()); t.start(); for (int i = 0; i < 20000; i++) { System.out.println("main:" + i); } } } ``` #### 3、实现Callable接口 新建类并实现`Callable`接口,重写函数`call()` ```java import java.util.concurrent.Callable; public class DemoCallable implements Callable { @Override public String call() throws RuntimeException { long result = 0; for (int i = 0; i < 20000; i++) { result += i; System.out.println(result); } return Long.toString(result); } } ``` 利用FutureTask类进行线程操作,依旧是使用Thread类进行创建线程,代码示例 ``` import DemoCallable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestDemoCallable { public static void main(String[] args) { FutureTask task = new FutureTask<>(new DemoCallable()); new Thread(task).start(); for (int i = 0; i < 20000; i++) { System.out.println("main:" + i); } try { System.out.println("计算结果:" + task.get()); } catch (InterruptedException | ExecutionException e) { System.out.println(e.getMessage()); } } } ``` Callable与Runnable接口的区别 | 特性 | `Runnable` | `Callable` | | :----------------- | :-------------------------------------------------------------- | :----------------------------------------------------------- | | **引入版本** | Java 1.0 | Java 5.0 (JUC) | | **核心方法** | `void run()` | `V call() throws Exception` | | **返回值** | **无** (`void`) | **有** (泛型 `V` 类型) | | **异常处理** | **不能抛出**受检异常(必须用try-catch处理) | **可以抛出**受检异常 | | **应用场景** | 异步执行,但不需要返回结果的任务 | 需要返回计算结果或处理异常的异步任务 | | **提交给线程池** | `ExecutorService.execute(Runnable)` 或 `submit(Runnable)` | `ExecutorService.submit(Callable)` | | **返回值对象** | `submit(Runnable)` 返回 `Future>`,但 `get()` 总是得到 `null` | `submit(Callable)` 返回 `Future`,可通过 `get()` 获取结果 | ## 三、线程的生命周期  #### 1、新建状态 (NEW) 线程对象已被创建,但还没有调用 `start()` 方法。此时它只是一个普通的 Java 对象,还没有获得任何线程相关的系统资源。 ```java Thread thread = new Thread(() -> { // 任务代码 }); System.out.println(thread.getState()); // 输出: NEW ``` #### 2、就绪状态 (RUNNABLE) 一旦调用了 `start()` 方法,线程就进入 **RUNNABLE** 状态。这个状态其实包含了两种子情况: * **就绪 (Ready)**:在就绪队列中,等待被操作系统线程调度器选中。 * **运行中 (Running)**:已经获得 CPU 时间片,正在执行。 **重要提示**:Java 将这两种情况合并为 `RUNNABLE`,因为我们无法从 JVM 层面精确区分一个线程是正在运行还是在等待 CPU。 ```java thread.start(); System.out.println(thread.getState()); // 输出: RUNNABLE ``` #### 3、阻塞状态 (BLOCKED) **仅与同步锁竞争相关**。当一个线程试图进入一个被 `synchronized` 关键字保护的同步代码块或方法时,如果该锁已被其他线程持有,那么该线程就会进入 **BLOCKED** 状态。**直到持有锁的线程释放锁,并且该线程成功竞争到锁之后,才会回到 RUNNABLE 状态。** ```java public class BlockedStateDemo { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { synchronized (lock) { // 长时间持有锁 while (true) { // 模拟长时间操作 } } }); Thread t2 = new Thread(() -> { synchronized (lock) { // 这个同步块会一直阻塞,因为t1持有着锁 System.out.println("t2 获得了锁"); } }); t1.start(); Thread.sleep(100); // 确保t1先启动并拿到锁 t2.start(); Thread.sleep(100); // 确保t2已经启动并开始争抢锁 System.out.println("t2 的状态: " + t2.getState()); // 输出: BLOCKED } } ``` #### 4、等待状态 (WAITING) 线程进入等待状态,需要**被其他线程显式唤醒**。进入此状态的方法: * `Object.wait()`:在没有超时参数的情况下调用。 * `Thread.join()`:在没有超时参数的情况下调用。 * `LockSupport.park()`:LockSupport 类的方法。 处于 **WAITING** 状态的线程正在等待另一个线程执行一个特定的操作。例如: * 一个调用了 `object.wait()` 的线程在等待另一个线程调用 `object.notify()` 或 `object.notifyAll()`。 * 一个调用了 `thread.join()` 的线程在等待目标线程 `thread` 终止。 ```java public class WaitingStateDemo { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread waitingThread = new Thread(() -> { synchronized (lock) { try { lock.wait(); // 释放锁,并进入WAITING状态 } catch (InterruptedException e) { e.printStackTrace(); } } }); waitingThread.start(); Thread.sleep(100); // 确保waitingThread执行到wait()方法 System.out.println("waitingThread 的状态: " + waitingThread.getState()); // 输出: WAITING // 唤醒waitingThread synchronized (lock) { lock.notify(); } } } ``` #### 5、超时等待状态 (TIMED\_WAITING) 与 **WAITING** 状态类似,但有一个**时间限制**。线程将在以下情况下进入此状态: * `Thread.sleep(long millis)` * `Object.wait(long timeout)` * `Thread.join(long millis)` * `LockSupport.parkNanos(long nanos)` * `LockSupport.parkUntil(long deadline)` 线程会保持这个状态直到超时时间到期**或者**被其他线程唤醒。 ```java public class TimedWaitingStateDemo { public static void main(String[] args) throws InterruptedException { Thread timedThread = new Thread(() -> { try { Thread.sleep(5000); // 睡眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } }); timedThread.start(); Thread.sleep(100); // 确保timedThread执行到sleep()方法 System.out.println("timedThread 的状态: " + timedThread.getState()); // 输出: TIMED_WAITING } } ``` #### 6、终止状态 (TERMINATED) 线程已经执行完毕。`run()` 方法正常结束,或者因为一个未捕获的异常而终止。处于此状态的线程不能再被启动(再次调用 `start()` 方法会抛出 `IllegalThreadStateException`)。 ```java public class TerminatedStateDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { // 简单的任务 }); thread.start(); thread.join(); // 主线程等待thread执行完毕 System.out.println("thread 的状态: " + thread.getState()); // 输出: TERMINATED } } ``` #### 7、生命周期总结 1. **不可逆性**:线程状态转换是单向的。例如,从 `NEW` 到 `RUNNABLE`,但不能返回;从 `RUNNABLE` 到 `TERMINATED` 后,线程就“死”了,无法复活。 2. **`BLOCKED` vs `WAITING`/`TIMED_WAITING`**: * `BLOCKED` 是由于**竞争同步锁失败**而被动进入的状态。 * `WAITING`/`TIMED_WAITING` 是线程**主动调用某些方法**(如 `wait`, `sleep`, `join`)而进入的状态。 3. **状态转换的触发者**: * 线程自身可以调用方法使自己进入 `WAITING`/`TIMED_WAITING`(如 `sleep(1000)`)。 * 线程必须**被其他线程的操作所影响**才能从 `BLOCKED` 或 `WAITING` 中出来(如锁被释放、被 `notify`)。 ## 四、常用方法 | 方法名 | 核心作用 | 备注/关键点 | | | | | :----------------------------- | :------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | | **`getName()`** | **获取当前线程名称** | 在多线程运行时方便日志记录。 | | | | | **`setName(String)`** | **设置线程名称** | 在多线程运行时方便日志记录。 | | | | | **`start()`** | **启动线程**,使其进入可运行状态。 | 只能调用一次,多次调用会抛`IllegalThreadStateException`。 | | | | | **`sleep(long)`** | **让当前正在执行的线程休眠**指定的毫秒数。 | 静态方法,休眠期间线程进入`TIMED_WAITING` 状态。最常用作模拟耗时或暂停。 | | | | | **`yield()`** | **提示调度器**当前线程愿意让出CPU。 | 静态方法。 yield() 只是一个**提示**,不能保证其他线程一定能获得执行权,且仅对优先级相同的线程有效。常用于调试或避免忙循环。 | | | | | **`join()`** | **等待目标线程执行终止**。 | 用于线程间同步,例如主线程等待子线程完成后再继续。可设置超时(`join(millis)`)。 | | | | | **`wait()`** | 使**当前线程等待**,直到其他线程调用此对象的 `notify()` 或 `notifyAll()`。 | **必须在同步代码块(synchronized)内调用**,否则会抛 `IllegalMonitorStateException`。 | | | | | **`notify()` / `notifyAll()`** | **唤醒**在此对象监视器上**等待的单个/所有线程**。 | **必须在同步代码块(synchronized)内调用**。 `notify()` 随机唤醒一个,`notifyAll()` 唤醒所有。 | | | | | **`setPriority(int)`** | **设置线程优先级**,提示调度器优先执行。 | 优先级范围1-10(MIN_PRIORITY-NORM_PRIORITY-MAX_PRIORITY)。**这只是一个提示,操作系统不一定采纳**,依赖性强。 | | | | Loading... ## 一、进程和线程的概念 #### 1.进程(Process) **什么是进程?** 你可以把一个**进程**理解为一个**独立的应用程序**。当你启动一个程序(比如打开一个 Word 文档,或者启动一个 Java 程序),操作系统就会为它创建一个进程。 **进程是操作系统进行资源分配和调度的基本单位。** 每个进程都拥有自己独立的一块内存空间、一套系统资源(如文件句柄、网络端口等)。一个进程崩溃后,在保护模式下,一般不会对其他进程产生影响。 **比喻:** 把进程想象成一个**独立的厨房**。这个厨房里有: * **专属的空间**:墙壁把它和别的厨房隔开。 * **专属的资源**:自己的灶台、锅碗瓢盆、食材。 * **独立的工作**:在这个厨房里可以单独做一道菜(比如红烧肉),不会直接干扰到另一个厨房做蛋糕。 #### 2. 线程(Thread) **什么是线程?** **线程**是**进程中的一个执行单元**,也被称为“轻量级进程”。一个进程可以包含多个线程,所有线程**共享进程的内存空间和资源**。 **线程是 CPU 调度和执行的基本单位。** 每个线程有自己独立的执行路径、程序计数器、栈和局部变量,但它们共享堆内存和全局变量。 **比喻:** 继续用厨房的比喻,把一个进程(厨房)里的**线程**想象成这个厨房里的**多个厨师**。 * **共享空间和资源**:所有厨师都在同一个厨房里工作,共享同一个灶台、冰箱、锅具。 * **分工协作**:一个厨师专门切菜,一个厨师专门炒菜,一个厨师专门装盘。他们协作完成一顿大餐。 * **需要协调**:如果两个厨师同时抢一把菜刀(共享资源),就会发生冲突。所以需要有“协调机制”(比如锁),来保证一个时间只有一个人能用菜刀。 ## 二、实现多线程的方式 #### 1、继承Thread类 多线程的启动是调用对象的`start()`方法,重写函数`run()` ```java public class DemoThread extends Thread { @Override public void run() { for (int i = 0; i < 20000; i++) { System.out.println(i); } } } ``` 示例代码 ```java import DemoThread; public class TestDemoThread { public static void main(String[] args) { DemoThread test1 = new DemoThread(); test1.start(); for (int i = 0; i < 20000; i++) { System.out.println("main:" + i); } } } ``` #### 2、实现Runnable接口 新建类并实现`Runnable`接口,重写函数`run()` ```java public class DemoRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 20000; i++) { System.out.println(i); } } } ``` 示例代码 ```java import DemoRunnable; public class TestDemoRunnable { public static void main(String[] args) { Thread t = new Thread(new DemoRunnable()); t.start(); for (int i = 0; i < 20000; i++) { System.out.println("main:" + i); } } } ``` #### 3、实现Callable接口 新建类并实现`Callable`接口,重写函数`call()` ```java import java.util.concurrent.Callable; public class DemoCallable implements Callable<String> { @Override public String call() throws RuntimeException { long result = 0; for (int i = 0; i < 20000; i++) { result += i; System.out.println(result); } return Long.toString(result); } } ``` 利用FutureTask类进行线程操作,依旧是使用Thread类进行创建线程,代码示例 ``` import DemoCallable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestDemoCallable { public static void main(String[] args) { FutureTask<String> task = new FutureTask<>(new DemoCallable()); new Thread(task).start(); for (int i = 0; i < 20000; i++) { System.out.println("main:" + i); } try { System.out.println("计算结果:" + task.get()); } catch (InterruptedException | ExecutionException e) { System.out.println(e.getMessage()); } } } ``` Callable与Runnable接口的区别 | 特性 | `Runnable` | `Callable` | | :----------------- | :-------------------------------------------------------------- | :----------------------------------------------------------- | | **引入版本** | Java 1.0 | Java 5.0 (JUC) | | **核心方法** | `void run()` | `V call() throws Exception` | | **返回值** | **无** (`void`) | **有** (泛型 `V` 类型) | | **异常处理** | **不能抛出**受检异常(必须用try-catch处理) | **可以抛出**受检异常 | | **应用场景** | 异步执行,但不需要返回结果的任务 | 需要返回计算结果或处理异常的异步任务 | | **提交给线程池** | `ExecutorService.execute(Runnable)` 或 `submit(Runnable)` | `ExecutorService.submit(Callable<V>)` | | **返回值对象** | `submit(Runnable)` 返回 `Future<?>`,但 `get()` 总是得到 `null` | `submit(Callable)` 返回 `Future<V>`,可通过 `get()` 获取结果 | ## 三、线程的生命周期  #### 1、新建状态 (NEW) 线程对象已被创建,但还没有调用 `start()` 方法。此时它只是一个普通的 Java 对象,还没有获得任何线程相关的系统资源。 ```java Thread thread = new Thread(() -> { // 任务代码 }); System.out.println(thread.getState()); // 输出: NEW ``` #### 2、就绪状态 (RUNNABLE) 一旦调用了 `start()` 方法,线程就进入 **RUNNABLE** 状态。这个状态其实包含了两种子情况: * **就绪 (Ready)**:在就绪队列中,等待被操作系统线程调度器选中。 * **运行中 (Running)**:已经获得 CPU 时间片,正在执行。 **重要提示**:Java 将这两种情况合并为 `RUNNABLE`,因为我们无法从 JVM 层面精确区分一个线程是正在运行还是在等待 CPU。 ```java thread.start(); System.out.println(thread.getState()); // 输出: RUNNABLE ``` #### 3、阻塞状态 (BLOCKED) **仅与同步锁竞争相关**。当一个线程试图进入一个被 `synchronized` 关键字保护的同步代码块或方法时,如果该锁已被其他线程持有,那么该线程就会进入 **BLOCKED** 状态。**直到持有锁的线程释放锁,并且该线程成功竞争到锁之后,才会回到 RUNNABLE 状态。** ```java public class BlockedStateDemo { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { synchronized (lock) { // 长时间持有锁 while (true) { // 模拟长时间操作 } } }); Thread t2 = new Thread(() -> { synchronized (lock) { // 这个同步块会一直阻塞,因为t1持有着锁 System.out.println("t2 获得了锁"); } }); t1.start(); Thread.sleep(100); // 确保t1先启动并拿到锁 t2.start(); Thread.sleep(100); // 确保t2已经启动并开始争抢锁 System.out.println("t2 的状态: " + t2.getState()); // 输出: BLOCKED } } ``` #### 4、等待状态 (WAITING) 线程进入等待状态,需要**被其他线程显式唤醒**。进入此状态的方法: * `Object.wait()`:在没有超时参数的情况下调用。 * `Thread.join()`:在没有超时参数的情况下调用。 * `LockSupport.park()`:LockSupport 类的方法。 处于 **WAITING** 状态的线程正在等待另一个线程执行一个特定的操作。例如: * 一个调用了 `object.wait()` 的线程在等待另一个线程调用 `object.notify()` 或 `object.notifyAll()`。 * 一个调用了 `thread.join()` 的线程在等待目标线程 `thread` 终止。 ```java public class WaitingStateDemo { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread waitingThread = new Thread(() -> { synchronized (lock) { try { lock.wait(); // 释放锁,并进入WAITING状态 } catch (InterruptedException e) { e.printStackTrace(); } } }); waitingThread.start(); Thread.sleep(100); // 确保waitingThread执行到wait()方法 System.out.println("waitingThread 的状态: " + waitingThread.getState()); // 输出: WAITING // 唤醒waitingThread synchronized (lock) { lock.notify(); } } } ``` #### 5、超时等待状态 (TIMED\_WAITING) 与 **WAITING** 状态类似,但有一个**时间限制**。线程将在以下情况下进入此状态: * `Thread.sleep(long millis)` * `Object.wait(long timeout)` * `Thread.join(long millis)` * `LockSupport.parkNanos(long nanos)` * `LockSupport.parkUntil(long deadline)` 线程会保持这个状态直到超时时间到期**或者**被其他线程唤醒。 ```java public class TimedWaitingStateDemo { public static void main(String[] args) throws InterruptedException { Thread timedThread = new Thread(() -> { try { Thread.sleep(5000); // 睡眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } }); timedThread.start(); Thread.sleep(100); // 确保timedThread执行到sleep()方法 System.out.println("timedThread 的状态: " + timedThread.getState()); // 输出: TIMED_WAITING } } ``` #### 6、终止状态 (TERMINATED) 线程已经执行完毕。`run()` 方法正常结束,或者因为一个未捕获的异常而终止。处于此状态的线程不能再被启动(再次调用 `start()` 方法会抛出 `IllegalThreadStateException`)。 ```java public class TerminatedStateDemo { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { // 简单的任务 }); thread.start(); thread.join(); // 主线程等待thread执行完毕 System.out.println("thread 的状态: " + thread.getState()); // 输出: TERMINATED } } ``` #### 7、生命周期总结 1. **不可逆性**:线程状态转换是单向的。例如,从 `NEW` 到 `RUNNABLE`,但不能返回;从 `RUNNABLE` 到 `TERMINATED` 后,线程就“死”了,无法复活。 2. **`BLOCKED` vs `WAITING`/`TIMED_WAITING`**: * `BLOCKED` 是由于**竞争同步锁失败**而被动进入的状态。 * `WAITING`/`TIMED_WAITING` 是线程**主动调用某些方法**(如 `wait`, `sleep`, `join`)而进入的状态。 3. **状态转换的触发者**: * 线程自身可以调用方法使自己进入 `WAITING`/`TIMED_WAITING`(如 `sleep(1000)`)。 * 线程必须**被其他线程的操作所影响**才能从 `BLOCKED` 或 `WAITING` 中出来(如锁被释放、被 `notify`)。 ## 四、常用方法 | 方法名 | 核心作用 | 备注/关键点 | | | | | :----------------------------- | :------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | | **`getName()`** | **获取当前线程名称** | 在多线程运行时方便日志记录。 | | | | | **`setName(String)`** | **设置线程名称** | 在多线程运行时方便日志记录。 | | | | | **`start()`** | **启动线程**,使其进入可运行状态。 | 只能调用一次,多次调用会抛`IllegalThreadStateException`。 | | | | | **`sleep(long)`** | **让当前正在执行的线程休眠**指定的毫秒数。 | 静态方法,休眠期间线程进入`TIMED_WAITING` 状态。最常用作模拟耗时或暂停。 | | | | | **`yield()`** | **提示调度器**当前线程愿意让出CPU。 | 静态方法。 yield() 只是一个**提示**,不能保证其他线程一定能获得执行权,且仅对优先级相同的线程有效。常用于调试或避免忙循环。 | | | | | **`join()`** | **等待目标线程执行终止**。 | 用于线程间同步,例如主线程等待子线程完成后再继续。可设置超时(`join(millis)`)。 | | | | | **`wait()`** | 使**当前线程等待**,直到其他线程调用此对象的 `notify()` 或 `notifyAll()`。 | **必须在同步代码块(synchronized)内调用**,否则会抛 `IllegalMonitorStateException`。 | | | | | **`notify()` / `notifyAll()`** | **唤醒**在此对象监视器上**等待的单个/所有线程**。 | **必须在同步代码块(synchronized)内调用**。 `notify()` 随机唤醒一个,`notifyAll()` 唤醒所有。 | | | | | **`setPriority(int)`** | **设置线程优先级**,提示调度器优先执行。 | 优先级范围1-10(MIN_PRIORITY-NORM_PRIORITY-MAX_PRIORITY)。**这只是一个提示,操作系统不一定采纳**,依赖性强。 | | | | 最后修改:2025 年 10 月 13 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。