Java 多线程全面笔记(一)
(一)多线程开篇
描述:多线程是 Java 并发编程的核心,用于充分利用 CPU 资源、提升程序响应速度。在单线程环境中,程序只能串行执行任务,遇到 IO 操作(如网络请求、文件读写)时会阻塞等待;而多线程可在一个线程阻塞时,让其他线程继续执行,避免 CPU 空闲。
核心应用场景:
- 后台任务(如日志收集、数据备份)
- 异步处理(如用户注册后发送短信 / 邮件)
- 高并发服务(如电商秒杀、API 接口)
(二)线程和进程的区别
进程
描述:进程是操作系统资源分配的基本单位。当程序(如 Chrome 浏览器、IDEA)被运行时,系统会从磁盘加载程序代码到内存,为其分配独立的内存空间(代码段、数据段、堆栈段)、CPU 时间片、IO 资源等,此时就形成一个进程。
示例:打开 Windows 任务管理器,能看到 “微信.exe”“Chrome.exe” 等进程,每个进程对应一个独立运行的程序实例。
线程
描述:线程是 CPU 调度的基本单位,隶属于进程。一个进程内可创建多个线程,所有线程共享进程的内存空间(如静态变量、堆内存),但拥有独立的栈内存(存储局部变量、方法调用栈)。每个线程对应一个 “指令流”,CPU 通过切换线程实现 “并发执行”。
示例:Chrome 浏览器的一个进程内,有 “页面渲染线程”“JS 执行线程”“网络请求线程” 等,多个线程协同完成浏览器功能。
线程与进程的区别
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 操作系统独立分配内存 / IO | 共享所属进程的资源 |
| 调度单位 | 操作系统不直接调度进程 | CPU 直接调度的基本单位 |
| 上下文切换成本 | 高(需切换内存空间、IO 资源) | 低(仅切换栈和寄存器) |
| 独立性 | 独立运行,互不影响 | 共享进程资源,相互依赖 |
(三)并行与并发的区别
单核 CPU(并发)
描述:单核 CPU 同一时间只能执行一个线程的指令。所谓 “并发”,是 CPU 通过 “时间片轮转” 机制,快速切换多个线程(如 1 秒内切换 1000 次),由于切换速度远超人类感知,造成 “多个线程同时运行” 的假象。示例:单核电脑上同时打开 “微信”“记事本”“浏览器”,CPU 会轮流给三个进程的线程分配时间片,每个线程执行几十毫秒后暂停,切换到下一个线程。
多核 CPU(并行 + 并发)
描述:多核 CPU(如 4 核、8 核)拥有多个独立的运算核心,可同时执行多个线程的指令(并行);同时,每个核心也会通过时间片轮转处理多个线程(并发)。示例:4 核 CPU 上运行 8 个线程,其中 4 个线程可在 4 个核心上 “并行执行”,剩余 4 个线程通过 “并发切换” 在核心上执行,整体效率远高于单核。
并行与并发的区别
| 概念 | 核心逻辑 | 依赖条件 | 示例 |
|---|---|---|---|
| 并发 | 时间片切换,“假同时” | 单核 / 多核 CPU | 单核电脑多任务运行 |
| 并行 | 多核心同时执行,“真同时” | 必须多核 CPU | 4 核 CPU 同时处理 4 个请求 |
(四)创建线程的方式有哪些
创建线程的四种方式
1. 继承 Thread 类
核心逻辑:继承 Thread 类,重写run()方法(定义线程执行的任务),通过start()方法启动线程(底层调用 JVM 的start0()方法,分配 CPU 资源)。
代码示例:
public class ThreadDemo extends Thread {
// 重写run()方法,定义线程任务
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
public static void main(String[] args) {
// 创建线程实例
ThreadDemo thread1 = new ThreadDemo();
ThreadDemo thread2 = new ThreadDemo();
// 启动线程(必须调用start(),而非直接调用run())
thread1.start(); // 线程1:输出0-4
thread2.start(); // 线程2:输出0-4(与线程1交替执行)
}
}
2. 实现 Runnable 接口
核心逻辑:实现Runnable接口的run()方法,将实例传入 Thread 构造器,通过 Thread 的start()启动线程。优点:避免单继承限制,可复用任务逻辑。
代码示例:
public class RunnableDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
public static void main(String[] args) {
// 创建任务实例
RunnableDemo task = new RunnableDemo();
// 传入Thread并启动
new Thread(task, "线程A").start(); // 线程A:输出0-4
new Thread(task, "线程B").start(); // 线程B:输出0-4
}
}
3. 实现 Callable 接口(带返回值)
核心逻辑:实现Callable<V>接口的call()方法(可返回泛型结果、抛出异常),通过FutureTask包装任务,传入 Thread 启动;最终通过FutureTask.get()获取返回值(会阻塞直到任务完成)。代码示例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo implements Callable<Integer> {
// 任务:计算1-100的和,返回结果
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws Exception {
// 1. 包装Callable任务
FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo());
// 2. 启动线程
new Thread(futureTask, "计算线程").start();
// 3. 获取返回值(阻塞等待任务完成)
Integer result = futureTask.get();
System.out.println("1-100的和:" + result); // 输出5050
}
}
4. 线程池创建线程(推荐)
核心逻辑:通过ThreadPoolExecutor创建线程池,复用线程资源(避免频繁创建 / 销毁线程的开销),通过execute()(无返回值)或submit()(带返回值)提交任务。
代码示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 创建线程池(核心参数:核心线程数、最大线程数、空闲时间、队列、拒绝策略)
ExecutorService threadPool = new ThreadPoolExecutor(
2, // 核心线程数:长期存活的线程
4, // 最大线程数:核心线程满+队列满后,最多再创建的线程
60, // 空闲时间:非核心线程空闲60秒后销毁
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2), // 任务队列:核心线程满后,任务暂存队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略:队列满+最大线程满时,抛出异常
);
// 2. 提交5个任务(核心2 + 队列2 + 非核心2,刚好处理5个任务)
for (int i = 1; i <= 5; i++) {
int taskId = i;
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "处理任务:" + taskId);
});
}
// 3. 关闭线程池(不再接受新任务,等待已提交任务完成)
threadPool.shutdown();
}
}
Runnable 和 Callable 的区别
| 对比维度 | Runnable | Callable<V> |
|---|---|---|
| 返回值 | 无(run () 返回 void) | 有(call () 返回 V 类型) |
| 异常处理 | 需在 run () 内部捕获,不能抛出 | 可在 call () 声明抛出异常 |
| 配合类 | Thread | FutureTask / 线程池 |
线程的run()和start()有什么区别
start():启动线程的唯一方法,会向 CPU 注册线程,等待时间片分配;一旦获得 CPU,会自动调用run()方法。只能调用一次(多次调用会抛IllegalThreadStateException)。run():仅定义线程的任务逻辑,直接调用run()会以 “主线程串行执行” 的方式运行,不会启动新线程。可调用多次。
(五)线程包括哪些状态,状态之间是如何变化的
线程的 6 种状态(Java 官方定义,基于Thread.State枚举)
| 状态名称 | 含义 | 触发场景 |
|---|---|---|
| NEW(新建) | 线程已创建(如new Thread()),但未调用start() | Thread thread = new Thread(); |
| RUNNABLE(可运行) | 线程已启动,正在等待 CPU 时间片(就绪)或正在执行(运行) | 调用thread.start()后 |
| BLOCKED(阻塞) | 线程因竞争synchronized锁失败,进入阻塞状态 | 线程 A 持有锁,线程 B 尝试获取同一锁时 |
| WAITING(无限等待) | 线程无时间限制等待,需被其他线程唤醒 | 调用Object.wait()、Thread.join()、LockSupport.park()(无超时) |
| TIMED_WAITING(计时等待) | 线程在指定时间内等待,超时后自动唤醒 | 调用Thread.sleep(1000)、Object.wait(1000)、LockSupport.parkNanos() |
| TERMINATED(终止) | 线程任务执行完毕(run()返回)或因异常终止 | run()方法执行完成、线程抛出未捕获异常 |
状态转换流程
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
// 1. RUNNABLE -> TIMED_WAITING(sleep 1秒)
Thread.sleep(1000);
// 2. 尝试获取锁(若被主线程持有,进入BLOCKED)
synchronized (ThreadStateDemo.class) {
System.out.println("线程获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// 3. run()执行完毕,进入TERMINATED
}, "测试线程");
// 初始状态:NEW
System.out.println("1. 线程状态:" + thread.getState()); // NEW
// 启动线程:NEW -> RUNNABLE
thread.start();
Thread.sleep(100); // 等待线程进入sleep
System.out.println("2. 线程状态:" + thread.getState()); // TIMED_WAITING
// 主线程持有锁,让测试线程进入BLOCKED
synchronized (ThreadStateDemo.class) {
Thread.sleep(2000); // 主线程持有锁2秒
}
// 等待线程执行完毕
Thread.sleep(1000);
System.out.println("3. 线程状态:" + thread.getState()); // TERMINATED
}
}
(六)新建T1,T2,T3三个线程,如何保证他们按顺序执行
使用 Thread.join () 方法(核心:让当前线程等待目标线程执行完毕)
原理:thread.join()会让调用方线程(如主线程)进入WAITING状态,直到thread执行完毕后,调用方线程才会继续执行。通过链式调用join(),可保证线程顺序。
代码示例:
public class ThreadOrderDemo {
public static void main(String[] args) throws InterruptedException {
// 定义三个线程,需按 T1 -> T2 -> T3 顺序执行
Thread t1 = new Thread(() -> System.out.println("T1执行"), "T1");
Thread t2 = new Thread(() -> System.out.println("T2执行"), "T2");
Thread t3 = new Thread(() -> System.out.println("T3执行"), "T3");
// 启动T1,主线程等待T1完成后再启动T2
t1.start();
t1.join(); // 主线程等待T1终止
// 启动T2,主线程等待T2完成后再启动T3
t2.start();
t2.join(); // 主线程等待T2终止
// 启动T3
t3.start();
t3.join();
System.out.println("所有线程执行完毕");
}
}
// 输出结果(严格顺序):
// T1执行
// T2执行
// T3执行
// 所有线程执行完毕
notify () 和 notifyAll () 的区别(补充)
notify():随机唤醒一个处于WAITING状态的线程(等待同一锁的线程),被唤醒的线程需重新竞争锁。notifyAll():唤醒所有处于WAITING状态的线程(等待同一锁的线程),所有被唤醒的线程共同竞争锁,只有一个能获得锁继续执行,其余进入BLOCKED状态。
代码示例(notifyAll ()):
public class NotifyDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
// 创建3个等待线程
for (int i = 1; i <= 3; i++) {
int threadId = i;
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程" + threadId + "进入等待");
lock.wait(); // 进入WAITING状态
System.out.println("线程" + threadId + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
// 主线程等待3秒后,唤醒所有等待线程
try {
Thread.sleep(3000);
synchronized (lock) {
lock.notifyAll(); // 唤醒所有等待线程
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 输出结果:
// 线程1进入等待
// 线程2进入等待
// 线程3进入等待
// (3秒后)
// 线程1被唤醒(随机,实际顺序取决于锁竞争)
// 线程2被唤醒
// 线程3被唤醒
(七)在java中wait和sleep方法的不同
| 对比维度 | wait() | sleep(long) |
|---|---|---|
| 所属类 | Object 类(所有对象都有) | Thread 类(静态方法) |
| 锁释放 | 会释放持有对象的锁 | 不会释放任何锁 |
| 调用条件 | 必须在synchronized代码块中调用 | 可在任意地方调用 |
| 唤醒方式 | 需其他线程调用notify()/notifyAll() | 超时自动唤醒,或interrupt()中断 |
| 状态转换 | RUNNABLE -> WAITING/TIMED_WAITING | RUNNABLE -> TIMED_WAITING |
代码示例(wait 释放锁,sleep 不释放锁):
public class WaitSleepDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
// 线程A:使用wait()
new Thread(() -> {
synchronized (lock) {
System.out.println("线程A持有锁,调用wait()");
try {
lock.wait(2000); // 释放锁,进入TIMED_WAITING
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A重新获得锁,执行完毕");
}
}, "A").start();
// 线程B:使用sleep()
new Thread(() -> {
synchronized (lock) {
System.out.println("线程B持有锁,调用sleep()");
try {
Thread.sleep(2000); // 不释放锁,进入
(八)如何停止一个正在运行的线程
停止线程的三种方式
1.使用退出标志,是线程正常退出,也就是当run方法完成后线程终止
2.使用stop方法强行终止(不推荐,方法已经作废)
3.使用interrupt方法中断线程
(1)打断阻塞的线程(sleep,wait,join)的线程,线程会抛出interruptedException异常
更多推荐


所有评论(0)