

✨博客主页: https://blog.csdn.net/m0_63815035?type=blog
💗《博客内容》:.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识
📢博客专栏: https://blog.csdn.net/m0_63815035/category_11954877.html
📢欢迎点赞 👍 收藏 ⭐留言 📝
📢本文为学习笔记资料,如有侵权,请联系我删除,疏漏之处还请指正🙉
📢大厦之成,非一木之材也;大海之阔,非一流之归也✨

这里写目录标题
- 前言
-
- 一、多线程概述
-
- [1.1 程序、进程、线程](#1.1 程序、进程、线程)
- [1.2 并发与并行](#1.2 并发与并行)
- [1.3 线程](#1.3 线程)
- [1.4 多线程的优缺点](#1.4 多线程的优缺点)
- 二、线程的创建与启动
-
- [2.1 方式一:继承 Thread 类](#2.1 方式一:继承 Thread 类)
- [2.2 方式二:实现 Runnable 接口](#2.2 方式二:实现 Runnable 接口)
- [2.3 方式三:实现 Callable 接口 + FutureTask(可获取返回值)](#2.3 方式三:实现 Callable 接口 + FutureTask(可获取返回值))
- [2.4 方式四:线程池(Executors)](#2.4 方式四:线程池(Executors))
- 三、线程的基本信息与控制
-
- [3.1 常用方法](#3.1 常用方法)
- [3.2 线程优先级示例](#3.2 线程优先级示例)
- [3.3 守护线程](#3.3 守护线程)
- 四、线程的生命周期与状态转换
- 五、线程安全与同步机制
- 六、线程间通信(等待/通知机制)
-
- [6.1 wait/notify/notifyAll 方法](#6.1 wait/notify/notifyAll 方法)
- [6.2 生产者消费者模型(单生产者单消费者)](#6.2 生产者消费者模型(单生产者单消费者))
- [6.3 多生产者多消费者问题](#6.3 多生产者多消费者问题)
- [6.4 Condition(Lock 配套的条件变量)](#6.4 Condition(Lock 配套的条件变量))
- 七、死锁与避免
-
- [7.1 死锁示例](#7.1 死锁示例)
- [7.2 死锁产生的四个必要条件](#7.2 死锁产生的四个必要条件)
- [7.3 避免死锁的方法](#7.3 避免死锁的方法)
- [八、volatile 关键字](#八、volatile 关键字)
-
- [8.1 内存可见性问题](#8.1 内存可见性问题)
- [8.2 volatile 的作用](#8.2 volatile 的作用)
- [8.3 适用场景](#8.3 适用场景)
- [九、线程池(Executor 框架)](#九、线程池(Executor 框架))
-
- [9.1 为什么要使用线程池](#9.1 为什么要使用线程池)
- [9.2 Executors 工具类提供的常见线程池](#9.2 Executors 工具类提供的常见线程池)
- [9.3 自定义线程池](#9.3 自定义线程池)
- [9.4 提交任务的方法](#9.4 提交任务的方法)
- [十、ThreadLocal 详解](#十、ThreadLocal 详解)
-
- [10.1 原理](#10.1 原理)
- [10.2 典型用法](#10.2 典型用法)
- [10.3 ThreadLocal vs synchronized](#10.3 ThreadLocal vs synchronized)
- 十一、高级并发工具简介(`java.util.concurrent`)
-
- [11.1 原子类(Atomic)](#11.1 原子类(Atomic))
- [11.2 并发集合](#11.2 并发集合)
- [11.3 CountDownLatch、CyclicBarrier、Semaphore](#11.3 CountDownLatch、CyclicBarrier、Semaphore)
- [11.4 CompletableFuture(异步编程)](#11.4 CompletableFuture(异步编程))
- 十二、最佳实践与常见误区
- 十三、结尾
前言
多线程是 Java 语言的核心特性之一,广泛应用于服务器端开发、GUI 程序、高并发系统等场景。理解多线程不仅需要掌握创建线程的方法,更需要深刻理解线程安全、同步机制、通信方式、死锁预防以及并发工具类。本文结合源码分析和实践案例,力求全面、深入、实用。
一、多线程概述
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。例如,一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。并且,还可以提高CPU的使用率。

1.1 程序、进程、线程
| 概念 | 描述 | 特点 |
|---|---|---|
| 程序(Program) | 静态的代码集合(如 .java、.class 文件) |
不占用运行资源 |
| 进程(Process) | 程序的一次动态执行,是系统资源分配的单位 | 每个进程有独立的地址空间(代码、数据、堆栈) |
| 线程(Thread) | 进程中的一条执行路径,是 CPU 调度的基本单位 | 同一进程内的线程共享堆和方法区,但拥有独立的程序计数器、栈和局部变量 |
一个进程至少有一个线程(主线程),也可以有多个线程。多线程的意义在于充分利用 CPU 时间片,提高程序响应速度和资源利用率。
1.2 并发与并行
- 并发(Concurrency) :在同一时间段内,多个任务交替执行(微观上串行,宏观上并行)。单核 CPU 通过快速时间片轮转实现并发。

- 并行(Parallelism) :在同一时刻,多个任务同时执行(需要多核 CPU)。

Java 多线程既可以实现并发(单核),也可以实现并行(多核)。并行流的底层使用
ForkJoinPool。
1.3 线程
线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,也可以有多个线程。
什么是多线程呢?即就是一个程序中有多个线程在同时执行,我们也称之为多线程程序。
紧接着,我们来区别单线程程序与多线程程序的不同:
1、单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务才开始执行。
2、多线程程序:即,若有多个任务可以同时执行。多个任务可以并发执行。
1.4 多线程的优缺点
优点:
- 提高 CPU 利用率
- 简化某些模型(如生产者消费者)
- 提升用户体验(UI 不卡顿)
缺点:
- 线程上下文切换开销(保存/恢复寄存器、程序计数器等)
- 数据竞争导致线程安全问题
- 死锁、活锁、饥饿等问题
- 调试复杂度增加
二、线程的创建与启动
Java 中创建线程有 三种主要方式 ,以及 线程池(第四种)。
2.1 方式一:继承 Thread 类
java
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行");
}
}
// 使用
MyThread t1 = new MyThread();
t1.start(); // 启动线程,JVM 调用 run()
要点:
- 重写
run()方法,编写任务代码。 - 调用
start()而非直接调用run()(直接调用 run 只是普通方法,不会创建新线程)。 - 每个线程对象只能
start()一次,重复调用会抛出IllegalThreadStateException。
2.2 方式二:实现 Runnable 接口
java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
// 使用
MyRunnable task = new MyRunnable();
Thread t = new Thread(task, "线程名");
t.start();
优点:避免单继承局限,更适合多个线程共享同一个任务对象(共享数据)。
2.3 方式三:实现 Callable 接口 + FutureTask(可获取返回值)
java
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
}
}
// 使用
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread t = new Thread(ft);
t.start();
Integer result = ft.get(); // 阻塞获取结果
特点:
call()方法有返回值,可以抛出受检异常。- 通过
FutureTask获取结果,get()会阻塞直到任务完成。
2.4 方式四:线程池(Executors)
后续章节详细讲解,此处简单示例:
java
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(() -> System.out.println("任务"));
pool.shutdown();
三、线程的基本信息与控制
3.1 常用方法
| 方法 | 说明 |
|---|---|
Thread.currentThread() |
获取当前正在执行的线程对象 |
getName() / setName(String) |
获取/设置线程名称 |
getPriority() / setPriority(int) |
优先级 1~10,默认为 5。优先级高只是获得 CPU 的概率高,不保证顺序 |
isAlive() |
线程是否处于活动状态(已启动且未死亡) |
Thread.sleep(long millis) |
让当前线程休眠(进入阻塞状态),不释放锁 |
yield() |
让出 CPU,从运行状态变为就绪状态,可能立即又被调度 |
join() |
等待该线程终止(当前线程阻塞直到目标线程结束) |
setDaemon(boolean) |
设置为守护线程(必须在 start 之前),当所有用户线程结束时 JVM 退出 |
interrupt() |
中断线程(设置中断标志) |
3.2 线程优先级示例
java
Thread high = new Thread(() -> {
for (int i = 0; i < 5; i++) System.out.println("high");
});
high.setPriority(Thread.MAX_PRIORITY);
Thread low = new Thread(() -> {
for (int i = 0; i < 5; i++) System.out.println("low");
});
low.setPriority(Thread.MIN_PRIORITY);
high.start();
low.start();
优先级并不能保证执行顺序,只是调度器的建议。
3.3 守护线程
java
Thread daemon = new Thread(() -> {
while (true) System.out.println("守护线程运行");
});
daemon.setDaemon(true);
daemon.start();
// 主线程结束,守护线程自动终止
守护线程常用于后台监控、垃圾回收等。注意:守护线程中不应该执行 I/O 等必须完成的操作,因为 JVM 退出时不会等待守护线程。
四、线程的生命周期与状态转换
Java 线程有六种状态(Thread.State 枚举):
| 状态 | 说明 |
|---|---|
| NEW | 线程对象已创建,但未调用 start() |
| RUNNABLE | 就绪(可运行)或正在运行,等待 CPU 时间片 |
| BLOCKED | 等待获取监视器锁(synchronized 块/方法) |
| WAITING | 无限期等待,需要其他线程显式唤醒(wait()、join() 无参) |
| TIMED_WAITING | 有限期等待,超时后自动唤醒(sleep(long)、wait(long)、join(long)) |
| TERMINATED | 线程执行结束(正常退出或异常) |
状态转换图(文字描述):
- NEW → RUNNABLE:调用
start() - RUNNABLE ↔ BLOCKED:竞争 synchronized 锁失败
- RUNNABLE → WAITING:调用
wait()、join()(无超时)、LockSupport.park() - WAITING → RUNNABLE:
notify()/notifyAll()或join线程结束 - RUNNABLE → TIMED_WAITING:调用
sleep()、带超时的wait()/join() - TIMED_WAITING → RUNNABLE:超时或唤醒
- RUNNABLE → TERMINATED:
run()正常结束或异常退出
注意 :
Thread.stop()已废弃,因为它会立即释放所有锁,可能导致数据不一致。
五、线程安全与同步机制
5.1 线程安全问题示例(卖票)
java
class TicketRunnable implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
try { Thread.sleep(1); } catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets-- + " 张票");
} else break;
}
}
}
运行后可能出现:
- 同一张票被多个线程卖出(负数票)
- 票数错乱
原因 :tickets > 0 判断后,线程被切换,另一个线程也进入判断,导致重复卖出。
5.2 synchronized 同步机制
synchronized 可以修饰代码块 或方法,保证同一时刻只有一个线程执行该代码块。
同步代码块
java
synchronized (lockObject) {
// 临界区代码
}
锁对象可以是任意对象,多个线程必须使用同一个锁对象才能互斥。
同步方法
- 非静态同步方法:锁是
this(当前实例) - 静态同步方法:锁是
Class对象(类名.class)
java
public synchronized void method() { } // 锁 this
public static synchronized void staticMethod() { } // 锁 Class
卖票问题解决方案:
java
class TicketRunnable implements Runnable {
private int tickets = 100;
private final Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (tickets <= 0) break;
System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets-- + " 张票");
}
}
}
}
5.3 synchronized 的可重入性
同一个线程已经持有锁,再次请求该锁时会成功(嵌套调用)。例如:
java
public synchronized void a() { b(); }
public synchronized void b() { }
线程调用 a() 时获得锁,调用 b() 时发现锁已被自己持有,直接进入,不会死锁。
5.4 Lock 接口与 ReentrantLock
synchronized 是隐式锁,加锁释放锁自动完成。Lock 是显式锁,需要手动加锁解锁。
java
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // 必须确保释放
}
ReentrantLock 特性:
- 可重入(与 synchronized 相同)
- 可中断锁:
lockInterruptibly() - 可超时:
tryLock(long time, TimeUnit unit) - 公平锁:构造参数
true表示按照等待时间顺序获取锁(默认非公平) - 可绑定多个 Condition 对象
示例:使用 ReentrantLock 实现卖票
java
class TicketRunnable implements Runnable {
private int tickets = 100;
private final Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (tickets <= 0) break;
System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets-- + " 张票");
} finally {
lock.unlock();
}
}
}
}
5.5 synchronized vs ReentrantLock
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 获取锁方式 | 隐式 | 显式 lock/unlock |
| 可中断 | 不支持(除非使用 wait 机制) |
支持 lockInterruptibly() |
| 超时尝试 | 不支持 | 支持 tryLock() |
| 公平锁 | 非公平 | 可指定公平/非公平 |
| 条件变量 | wait/notify(一个等待集) |
多个 Condition |
| 性能 | 优化后接近 | 高并发下略优 |
| 语法简洁性 | 简洁 | 需注意 finally 释放 |
选择建议 :一般情况下优先使用 synchronized,代码更简单;需要高级特性(如可中断、超时、多条件)时使用 ReentrantLock。
六、线程间通信(等待/通知机制)
6.1 wait/notify/notifyAll 方法
这些方法定义在 Object 类中,必须在 synchronized 代码块或方法中调用,且必须通过锁对象调用。
wait():使当前线程进入 WAITING 状态,并释放锁。notify():唤醒在同一个锁上等待的一个线程(具体哪个由 JVM 决定)。notifyAll():唤醒所有等待线程。
错误用法 :调用
wait()和notify()时没有持有锁会抛出IllegalMonitorStateException。
6.2 生产者消费者模型(单生产者单消费者)
使用 wait/notify 实现简单的交替生产消费。
java
class Resource {
private String data;
private boolean hasData = false;
public synchronized void produce(String value) throws InterruptedException {
while (hasData) { // 用 while 防止虚假唤醒
wait();
}
data = value;
hasData = true;
System.out.println("生产:" + data);
notify(); // 通知消费者
}
public synchronized void consume() throws InterruptedException {
while (!hasData) {
wait();
}
System.out.println("消费:" + data);
hasData = false;
notify();
}
}
6.3 多生产者多消费者问题
使用 while 替代 if(防止虚假唤醒)和 notifyAll(避免某些线程永远等不到唤醒)。但 notifyAll 会唤醒所有等待线程(包括同类线程),导致效率下降。
更好的解决方案 :使用 Lock + Condition 实现定向唤醒。
6.4 Condition(Lock 配套的条件变量)
java
class Resource {
private String data;
private boolean hasData = false;
private final Lock lock = new ReentrantLock();
private final Condition producerCond = lock.newCondition();
private final Condition consumerCond = lock.newCondition();
public void produce(String value) throws InterruptedException {
lock.lock();
try {
while (hasData) {
producerCond.await();
}
data = value;
hasData = true;
System.out.println("生产:" + data);
consumerCond.signal(); // 只唤醒消费者
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (!hasData) {
consumerCond.await();
}
System.out.println("消费:" + data);
hasData = false;
producerCond.signal(); // 只唤醒生产者
} finally {
lock.unlock();
}
}
}
Condition的await()、signal()、signalAll()替代wait、notify、notifyAll,且可以区分不同的等待队列。
七、死锁与避免
7.1 死锁示例
java
Object lockA = new Object();
Object lockB = new Object();
Thread t1 = new Thread(() -> {
synchronized (lockA) {
sleep(100);
synchronized (lockB) { /* ... */ }
}
});
Thread t2 = new Thread(() -> {
synchronized (lockB) {
sleep(100);
synchronized (lockA) { /* ... */ }
}
});
两个线程相互等待对方释放锁,导致无限阻塞。
7.2 死锁产生的四个必要条件
- 互斥:资源一次只能被一个线程使用。
- 请求与保持:线程持有至少一个资源,并等待其他资源。
- 不可剥夺:资源只能由持有者主动释放。
- 循环等待:存在等待环。
7.3 避免死锁的方法
- 破坏循环等待:按固定顺序获取锁(如总是先锁 A 后锁 B)。
- 使用
tryLock超时放弃。 - 使用
Lock的可中断锁机制。 - 减少同步代码块嵌套。
八、volatile 关键字
8.1 内存可见性问题
在多核 CPU 中,每个线程可能将共享变量缓存到 CPU 缓存中,导致一个线程修改了变量但另一个线程不可见。
8.2 volatile 的作用
- 保证可见性:对一个 volatile 变量的写操作会立即刷新到主内存,读操作从主内存读取。
- 禁止指令重排序:编译器和处理器不会对 volatile 变量相关的操作进行重排序。
volatile 不保证原子性 。例如
volatile int count; count++仍然是线程不安全的(因为读-改-写不是原子操作)。
8.3 适用场景
- 布尔标志位(如
volatile boolean running = true;) - 一次性安全发布(如双重检查锁中的
volatile禁止重排序)
java
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
}
九、线程池(Executor 框架)
9.1 为什么要使用线程池
- 避免频繁创建/销毁线程的开销。
- 限制并发线程数量,防止资源耗尽。
- 统一管理线程的生命周期。
9.2 Executors 工具类提供的常见线程池
| 方法 | 说明 | 适用场景 |
|---|---|---|
newFixedThreadPool(int n) |
固定大小线程池,无界队列 | 任务数量可控,限制最大并发 |
newSingleThreadExecutor() |
单线程池,顺序执行 | 保证任务串行 |
newCachedThreadPool() |
可缓存线程池,空闲 60 秒回收 | 大量短任务,弹性伸缩 |
newScheduledThreadPool(int core) |
支持定时/延迟任务 | 调度任务 |
示例:
java
ExecutorService fixed = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
fixed.execute(() -> System.out.println(Thread.currentThread().getName()));
}
fixed.shutdown();
9.3 自定义线程池
ThreadPoolExecutor 提供更细致的参数:
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), // 工作队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
拒绝策略:
AbortPolicy:抛出异常(默认)CallerRunsPolicy:由提交任务的线程执行DiscardPolicy:丢弃任务DiscardOldestPolicy:丢弃队列头部任务,重试提交
9.4 提交任务的方法
execute(Runnable):无返回值submit(Callable):返回Future<T>submit(Runnable):返回Future<?>invokeAll(Collection<Callable>):批量执行并等待所有完成invokeAny(...):执行集合,返回第一个完成的结果
十、ThreadLocal 详解
10.1 原理
ThreadLocal 为每个线程提供独立的变量副本,每个线程修改的是自己的副本,互不干扰。底层是每个 Thread 对象持有一个 ThreadLocalMap,ThreadLocal 作为键,副本值作为值。
10.2 典型用法
java
public class ThreadLocalExample {
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static void main(String[] args) {
Runnable task = () -> {
System.out.println(dateFormat.get().format(new Date()));
// 不需要显式 remove,但建议在使用结束后调用 remove() 避免内存泄漏
};
new Thread(task).start();
}
}
注意:使用线程池时,线程复用可能导致 ThreadLocal 值残留,必须在使用后调用
remove()。
10.3 ThreadLocal vs synchronized
synchronized:以时间换空间,多个线程排队访问共享数据。ThreadLocal:以空间换时间,每个线程拥有自己的副本,无需同步。
十一、高级并发工具简介(java.util.concurrent)
11.1 原子类(Atomic)
如 AtomicInteger、AtomicReference,使用 CAS 实现无锁线程安全。
java
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子加1
11.2 并发集合
ConcurrentHashMap:分段锁(JDK7)/ CAS+synchronized(JDK8)CopyOnWriteArrayList:读多写少场景BlockingQueue:阻塞队列(ArrayBlockingQueue,LinkedBlockingQueue)
11.3 CountDownLatch、CyclicBarrier、Semaphore
- CountDownLatch:一个线程等待多个线程完成。
- CyclicBarrier:多个线程互相等待,达到屏障后一起执行。
- Semaphore:控制同时访问资源的线程数量(限流)。
11.4 CompletableFuture(异步编程)
Java 8 引入,支持链式异步回调。
java
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println);
十二、最佳实践与常见误区
- 不要使用
Thread.stop()、suspend()、resume(),已废弃且不安全。 - 使用
volatile或原子类处理简单的状态标志,避免过度同步。 - 优先使用
synchronized而非Lock,除非需要额外功能。 - 注意死锁 :尽量按相同顺序获取锁,使用
tryLock超时。 - 线程池使用完毕要
shutdown(),否则程序可能不会退出。 - 不要在
run()方法中抛出受检异常 ,只能使用try-catch。 - 避免在持有锁时调用外部方法(可能引起死锁或性能问题)。
- 使用
ThreadLocal后务必remove()(尤其在线程池中)。
十三、结尾
多线程是 Java 进阶的必经之路,理解线程的生命周期、同步机制和通信方式是核心。掌握 synchronized、Lock、volatile 的区别与使用场景,熟悉 wait/notify 和 Condition,能够设计出正确且高效的多线程程序。同时,线程池和并发工具类大大简化了并发编程的复杂度,是实际项目中的首选。
最后,多线程问题的调试往往比较困难,建议多用 jstack、jconsole、visualvm 等工具分析线程状态,逐步积累经验。
csharp
今天这篇文章就到这里了,大厦之成,非一木之材也;大海之阔,非一流之归也。感谢大家观看本文
