Java 多线程基础的分享
1. 多线程的重要性
在现代软件开发体系中,多线程编程是提升程序执行效率、充分利用多核 CPU 资源、实现高并发处理的关键技术。无论是服务端接口、消息队列、定时任务,还是大数据计算、分布式框架,都离不开多线程的支撑
2 多线程核心基础概念
2.1 进程与线程
进程:进程是操作系统进行资源分配与调度的基本单位,是程序运行的独立载体。每个进程拥有独立的内存空间、文件句柄、环境变量等资源,进程间相互隔离,一个进程崩溃通常不影响其他进程。
线程:线程是进程内部的执行单元,也是 CPU 调度与执行的最小单位。一个进程可包含一至多个线程,同一进程内的线程共享堆内存、方法区、文件描述符等资源,但拥有独立的程序计数器、虚拟机栈与本地方法栈。
核心区别
- 资源:进程拥有独立资源,线程共享所属进程资源。
- 开销:进程创建、切换、销毁开销大,线程更轻量。
- 通信:进程间通信复杂,线程间通信便捷高效。
- 稳定性:进程隔离性强,线程崩溃可能导致整个进程退出。
2.2 并发与并行
并发:单个 CPU 核心通过时间片轮转交替执行多个线程
并行:多个 CPU 核心同时执行多个线程
2.3 同步与异步
同步:调用方必须等待任务执行完成才能继续后续逻辑,整个过程等待。
异步:调用方无需等待任务完成,可直接处理其他逻辑;任务执行完毕后通过通知、回调等机制返回结果,非阻塞。
2.4 多线程的优势与风险
优势
- 提高 CPU 利用率,减少资源闲置。
- 提升程序响应速度,耗时任务后台执行。
- 模块化拆分,复杂任务并行处理。
- 支持高并发服务端场景,提升系统吞吐量。
风险
- 线程安全问题:共享数据竞争导致结果错乱。
- 死锁:线程相互等待资源,程序永久阻塞。
- 上下文切换开销:线程过多降低整体效率。
- 内存溢出:线程栈占用过多内存
3 Java 创建线程的三种方式
Java 提供三种标准线程创建方式,适用于不同业务场景。
3.1 继承 Thread 类
实现步骤
- 自定义类继承
java.lang.Thread。 - 重写
run()方法,编写线程执行逻辑。 - 创建实例并调用
start()启动线程。
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("Thread-01");
t2.setName("Thread-02");
t1.start();
t2.start();
}
}
注意:必须调用 start(),直接调用 run() 仅为普通方法执行,不开启新线程。
优缺点
- 优点:实现简单,可直接使用 Thread 类 API。
- 缺点:Java 单继承限制,无法继承其他类;任务与线程耦合,复用性差。
3.2 实现 Runnable 接口
解决单继承局限,是最常用的创建方式。
实现步骤
- 自定义类实现
Runnable接口。 - 重写
run()方法。 - 将任务对象传入 Thread 构造方法。
- 调用
start()启动线程。
// 父类
class Animal {
public void eat() {
System.out.println("动物会吃饭");
}
}
// 继承 Animal + 实现 Runnable = 既能当动物,又能当线程
class Dog extends Animal implements Runnable {
@Override
public void run() {
System.out.println("小狗线程正在运行");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 调用父类方法
// 把 dog 变成线程
Thread thread = new Thread(dog);
thread.start();
}
}
优缺点
- 优点:避免单继承限制
- 缺点:无返回值,无法抛出受检异常。
3.3 实现 Callable 接口 + FutureTask
支持返回值与异常抛出,适用于需要获取执行结果的场景。
实现步骤
- 自定义类实现
Callable<V>接口。 - 重写
call()方法。 - 封装为
FutureTask。FutureTask是 Java 官方给你写好的 “适配器”让Callable能被Thread接收(因为它实现了Runnable);帮你保存Callable的执行结果,让你能通过get()拿到返回值。 - 传入 Thread 并启动。
- 通过
get()获取结果。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
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;
}
}
public class CallableTest {
public static void main(String[] args) throws Exception {
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread t = new Thread(task);
t.start();
Integer result = task.get();
System.out.println("执行结果:" + result);
}
}
优缺点
- 优点:支持返回值;可抛出受检异常。
- 缺点:实现稍复杂;
get()会阻塞主线程。
3.4 三种方式对比
表格
| 方式 | 继承限制 | 返回值 | 异常 | 复用性 | 适用场景 |
|---|---|---|---|---|---|
| 继承 Thread | 单继承 | 无 | 不支持 | 差 | 简单无复用任务 |
| 实现 Runnable | 无 | 无 | 不支持 | 好 | 通用无返回任务 |
| 实现 Callable | 无 | 有 | 支持 | 好 | 需要结果的异步任务 |
4 Thread 类常用 API 详解
4.1 常用构造方法
Thread()Thread(String name)Thread(Runnable target)Thread(Runnable target, String name)
4.2 核心方法
start():启动线程,进入就绪状态。run():线程执行体。getName()/setName():获取 / 设置线程名。currentThread():获取当前执行线程。sleep(long):休眠指定毫秒,进入计时等待。setPriority(int):设置优先级(1–10)。setDaemon(boolean):设置为守护线程。
4.3 线程优先级
优先级仅为调度建议,不保证执行顺序:
4.4 守护线程
守护线程为用户线程提供后台服务,所有用户线程结束后,守护线程自动退出
thread.setDaemon(true);
thread.start();
5 线程的生命周期与状态转换
Java 线程共 6 种状态:
- 新建:创建线程对象,未调用
start()。 - 就绪:调用
start(),等待 CPU 时间片。 - 阻塞:等待锁释放。
- 等待:
wait()/join(),无超时。 - 计时等待:
sleep()/wait(long)。 - 终止:执行完毕或异常退出。

6 线程安全问题与成因
6.1 什么是线程安全
多个线程同时读写共享数据时,因执行顺序不可控导致结果与预期不一致,称为线程不安全。
6.2 不安全成因
- 存在共享数据。
- 多线程同时执行修改操作。
典型场景:多线程同时对同一变量执行自增操作。
7 同步机制与锁实现
Java 提供三种线程安全解决方案:
7.1 同步代码块
synchronized(锁对象) {
// 共享数据操作
}
锁对象可以是任意对象,同一时刻仅一个线程可持有。
7.2 同步方法
public synchronized void method() {
// 同步逻辑
}
- 非静态方法:锁为
this。 - 静态方法:锁为当前类 Class 对象。
同步方法 vs 同步代码块
同步方法其实就是把整个方法包成了一个同步代码块,等价于:
public void saleTicket() {
synchronized (this) {
// 方法体全部内容
}
}
而同步代码块可以只锁关键代码:
public boolean saleTicket() {
// 不操作共享数据的代码可以写在外面,不用等锁
synchronized (this) {
if (tickets > 0) {
// ...
return false;
}
return true;
}
}
7.3 Lock 显式锁
ReentrantLock 是常用实现,比 synchronized 更灵活。
Lock lock = new ReentrantLock();
try {
lock.lock();
// 执行代码
} finally {
lock.unlock();
}
7.4 synchronized 与 Lock 对比
synchronized:隐式锁,自动释放,性能高,功能简洁。Lock:显式锁,手动释放,支持公平锁、超时、中断。
8 死锁的产生与避免
8.1 死锁产生的四个必要条件
- 互斥条件:资源同一时间仅一个线程持有。
- 请求与保持:持有资源并继续请求其他资源。
- 不可剥夺:资源只能由持有线程主动释放。
- 循环等待:线程形成资源等待环路。
8.2 避免死锁的常用方案
- 避免嵌套锁。
- 统一加锁顺序。
- 使用带超时的锁。
9 生产者消费者模型
用处: 生产消费速度不匹配、线程协作同步
基于 wait/notify/notifyAll 实现等待唤醒机制,必须在 synchronized 内部使用。
wait():释放锁,进入等待队列。notify():随机唤醒一个等待线程。notifyAll():唤醒所有等待线程。
生产者消费者模型
- 仓库满:生产者等待。
- 仓库空:消费者等待。
// 仓库类
class Warehouse {
private int goods = 0; // 商品数量
private final int MAX = 5; // 仓库最大容量
// 生产者方法:往仓库放商品
public synchronized void produce() throws InterruptedException {
// 仓库满了,生产者等待
while (goods >= MAX) {
wait(); // 释放锁,进入等待
}
// 没满,继续生产
goods++;
System.out.println(Thread.currentThread().getName() + " 生产了商品,当前库存:" + goods);
// 叫醒消费者来消费
notifyAll();
}
// 消费者方法:从仓库拿商品
public synchronized void consume() throws InterruptedException {
// 仓库空了,消费者等待
while (goods <= 0) {
wait(); // 释放锁,进入等待
}
// 没空,继续消费
goods--;
System.out.println(Thread.currentThread().getName() + " 消费了商品,当前库存:" + goods);
// 叫醒生产者来生产
notifyAll();
}
}
public class Test {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
// 生产者线程
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
warehouse.produce();
Thread.sleep(100); // 模拟生产耗时
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者").start();
// 消费者线程
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
warehouse.consume();
Thread.sleep(200); // 模拟消费耗时
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者").start();
}
}
10 阻塞队列原理与使用
阻塞队列是封装好的、专门用来极简实现生产者消费者的工具
阻塞队列是线程安全的队列,内置等待唤醒逻辑,是实现生产者消费者的最优方案。
10.1 常用阻塞队列
ArrayBlockingQueue:数组实现,有界。LinkedBlockingQueue:链表实现,可选有界。PriorityBlockingQueue:优先级队列。SynchronousQueue:不存储元素,直接传递。
10.2 核心方法
put():队列满则阻塞。take():队列空则阻塞。
11 线程池原理与四大内置线程池
11.1 线程池优势
- 复用线程,避免频繁创建销毁开销。
- 控制并发线程数,防止资源耗尽。
- 统一管理、监控、调优。
11.2 四大内置线程池
-
newFixedThreadPool(n)固定线程数,无界队列,适用于任务量稳定场景。
-
newCachedThreadPool()无核心线程,可自动扩容,适用于大量短生命周期任务。
-
newSingleThreadExecutor()单线程串行执行,保证任务顺序。
-
newScheduledThreadPool(n)支持定时、周期执行任务。
12 自定义线程池实现
ThreadPoolExecutor 是线程池的核心实现类。
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(100), // 阻塞队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
12.1 七大参数
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲时间
- unit:时间单位
- workQueue:阻塞队列
- threadFactory:线程工厂
- handler:拒绝策略


13 多线程常见面试要点总结
- 进程与线程的区别。
- 并发与并行的区别。
- 创建线程的三种方式及对比。
start()与run()的区别。- 线程的六种状态及转换。
- 线程安全成因及三种解决方案。
synchronized与 Lock 的区别。- 死锁的四个条件与避免方案。
- 线程池优势、七大参数、四大内置线程池。
更多推荐


所有评论(0)