一、什么是线程、进程
- 进程:正在运行的程序,操作系统资源分配的最小单位;进程之间相互独立。
- 线程:进程内部的执行路径(执行单元),CPU 调度的最小单位。一个进程至少有 1 个主线程。
多线程:同一个进程内,多条线程并发执行,抢占式调度(CPU 随机分配时间片)。
并发 vs 并行
- 并发:同一时间段,多个任务交替执行(单核 CPU,看起来同时跑)
- 并行:同一时刻,多个任务同时执行(多核 CPU)
二、创建线程的 3 种方式
方式 1:继承 Thread 类
- 定义类继承
Thread - 重写
run():线程要执行的任务 - 创建子类对象,调用
start()启动线程(不是直接调用 run ()!run 只是普通方法)
java
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程执行");
}
}
//使用
MyThread t = new MyThread();
t.start();
缺点:Java 单继承,继承 Thread 后不能再继承其他类。
方式 2:实现 Runnable 接口(推荐)
- 类实现
Runnable接口 - 重写
run() - 把 Runnable 实现类对象传入 Thread 构造器,调用
start()
java
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("runnable任务");
}
}
//使用
Runnable run = new MyRunnable();
Thread t = new Thread(run);
t.start();
优点:避免单继承限制;任务和线程对象解耦,多个线程共用同一个任务对象。
方式 3:Callable + FutureTask(带返回值、可抛异常)
- Runnable 的 run 无返回值,不能抛出受检异常;Callable 可以返回结果。
java
//1.创建Callable实现类
class MyCall implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 100;
}
}
//2.包装Callable到FutureTask
FutureTask<Integer> task = new FutureTask<>(new MyCall());
//3.传入Thread
Thread t = new Thread(task);
t.start();
//获取返回结果,会阻塞等待线程执行完成
Integer res = task.get();
三、Thread 常用 API
| 方法 | 作用 |
|---|---|
void start() |
启动线程,自动调用 run () |
void run() |
线程任务方法 |
static void sleep(long ms) |
静态方法,让当前线程休眠指定毫秒,阻塞,抛出 InterruptedException |
String getName() |
获取线程名 |
void setName(String name) |
设置线程名 |
static Thread currentThread() |
获取当前正在执行的线程对象 |
void join() |
等待该线程执行完毕,其他线程阻塞 |
void setDaemon(true) |
设置为守护线程(后台线程,主线程结束守护线程自动结束,如垃圾回收 GC) |
void interrupt() |
中断线程(不是立刻停止,是标记中断状态) |
重点:
sleep()是静态方法,哪个线程执行 sleep,哪个线程休眠,不是写在哪个类就休眠哪个。
四、线程安全问题
1. 线程安全出现条件
- 多线程并发
- 多个线程共享同一个资源
- 共享资源存在写操作(修改)
经典案例:售票,多个线程同时卖票,出现重复票、负数票。
2. 线程同步(3 种解决方案,解决线程安全)
核心思想:加锁,同一时刻只允许一个线程进入临界区操作共享数据。
① 同步代码块 synchronized
java
synchronized(锁对象){
// 操作共享数据的代码(临界区)
}
- 锁对象:任意同一个对象;多个线程必须共用同一把锁!
- 执行逻辑:线程进入代码块,获取锁;执行完毕释放锁;其他线程阻塞等待锁。
② 同步方法 synchronized
把synchronized写在方法上
java
//实例方法:锁对象默认 this
public synchronized void sellTicket(){
}
//静态方法:锁对象是 类名.class
public static synchronized void test(){
}
③ Lock 锁(JDK5 后,手动锁,更灵活)
ReentrantLock 可重入锁
java
Lock lock = new ReentrantLock();
lock.lock(); //手动加锁
try{
//共享资源操作
}finally{
lock.unlock(); //手动释放锁,写finally防止异常不释放锁!
}
synchronized:自动加锁释放;Lock 手动控制,适合复杂场景。
面试区分:synchronized 是内置锁,非公平锁;ReentrantLock 支持公平 / 非公平锁、可中断。
五、线程池
为什么要用线程池
频繁创建销毁线程开销大;线程池复用线程,控制线程数量,防止 CPU 过载。 核心思想:提前创建一批线程,任务提交给池子,线程做完任务归还池子复用。
创建线程池方式
- Executors 工具类(简单,不推荐生产)
Executors.newFixedThreadPool(固定线程数)
ThreadPoolExecutor原生构造器(生产推荐,7 大参数)- 核心参数:核心线程数、最大线程数、空闲存活时间、阻塞队列、线程工厂、拒绝策略

5.1 ThreadPoolExecutor 七大参数详解(PPT 员工比喻:正式工 + 临时工)
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数量(正式员工)。线程池创建后,默认不会销毁,长期保留。
- maximumPoolSize:最大线程数量。核心线程 + 临时线程的总数上限。
- keepAliveTime:临时线程空闲存活时间。临时线程空闲超过这个时间,就会被销毁。
- unit :时间单位,配合
keepAliveTime(TimeUnit.SECONDS 等)。 - workQueue:任务阻塞队列。任务提交,如果核心线程都忙,任务进入队列排队。
- threadFactory:线程工厂,用来创建线程对象,可以自定义线程名称。
- handler:拒绝策略。当核心线程、临时线程全部繁忙,队列也满了,新任务触发拒绝策略。
员工比喻: corePoolSize=3(3 个正式工),maximumPoolSize=5(最多一共 5 个人),临时线程 = 5-3=2 个临时工。临时工空闲太久就开除;正式工一直保留。
5.2 线程池任务执行流程(重点!面试必考)
- 提交任务,判断核心线程数是否未满 → 有空闲核心线程,直接创建核心线程执行任务。
- 核心线程全部繁忙,判断阻塞队列是否已满 → 队列没满,任务放进队列排队。
- 队列也满了,判断当前总线程数是否小于最大线程数 → 还没到上限,创建临时线程执行任务。
✅触发临时线程条件:核心线程全部忙 + 队列满 + 总线程数 < maximumPoolSize
- 总线程数已经达到最大值,此时新任务进来 → 触发拒绝策略。
一句话流程:核心线程 → 队列排队 → 创建临时线程 → 拒绝任务
5.3 四大拒绝策略(RejectedExecutionHandler)
表格
| 拒绝策略 | 行为说明 |
|---|---|
AbortPolicy(默认) |
丢弃任务,并抛出RejectedExecutionException异常 |
DiscardPolicy |
默默丢弃任务,不抛异常(生产不推荐,任务丢失无感知) |
DiscardOldestPolicy |
丢掉队列里等待最久的任务,尝试把当前新任务入队 |
CallerRunsPolicy |
不丢弃,由提交任务的主线程来执行这个任务 |
5.4 Executors 工具类创建线程池(了解,生产禁止使用)
Executors 底层本质还是调用ThreadPoolExecutor,只是封装了参数,开发简单,但存在隐患:
Executors.newFixedThreadPool(n):固定线程池,核心线程 = n,无临时线程,队列长度为Integer.MAX_VALUE。任务堆积会造成 OOM 内存溢出。Executors.newSingleThreadExecutor():单线程池,只有 1 个核心线程。队列容量极大,容易 OOM。Executors.newCachedThreadPool():可缓存线程池,无核心线程,临时线程上限 MAX_VALUE,大量任务会疯狂创建线程,CPU 爆满。
开发规范:阿里巴巴开发手册禁止在生产使用 Executors 创建线程池 ,强制手写
ThreadPoolExecutor,自己指定队列长度、最大线程数,规避 OOM 风险。
5.5 线程池常用方法
| 方法 | 作用 |
|---|---|
execute(Runnable task) |
提交无返回值任务 |
submit(Callable task) |
提交带返回值任务,返回 Future 对象,可以获取结果 |
shutdown() |
平缓关闭线程池:不再接收新任务,等待已提交任务全部执行完毕再关闭 |
shutdownNow() |
立刻关闭,尝试中断正在执行任务,队列未执行任务直接返回 |
5.6 代码示例(手写 ThreadPoolExecutor)
java
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 7个参数手动创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //核心线程
5, //最大线程
2, //临时线程空闲存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), //阻塞队列容量10
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略
);
//提交任务
for (int i = 1; i <= 20; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
pool.shutdown();
}
}
5.7 面试简答背诵
- 什么时候创建临时线程?
核心线程全部繁忙,任务队列已经满,并且当前线程总数还没达到 maximumPoolSize,才创建临时线程。
- 什么时候触发拒绝策略?
核心线程、临时线程全部繁忙,任务队列已满,新来任务触发拒绝策略。
- 为什么不推荐 Executors?
底层队列或者最大线程数是极大值,高并发场景下容易出现 OOM 内存溢出,生产推荐手动创建 ThreadPoolExecutor。
5.8 小结
- 线程池核心目的:复用线程对象,减少频繁创建销毁线程带来的性能损耗,可控并发数量。
- 任务执行顺序:核心线程优先 → 队列排队 → 临时线程 → 拒绝策略。
- 四大拒绝策略:默认抛异常、丢弃无异常、丢弃最老任务、调用者执行。
线程池执行流程
- 任务进来,核心线程未满 → 创建核心线程执行
- 核心线程满了 → 任务放入阻塞队列排队
- 队列满 → 创建非核心线程,直到达到最大线程数
- 最大线程也满 → 触发拒绝策略(丢弃任务、抛异常等)
六、线程状态(6 种)
- NEW:新建,new Thread (),还没 start
- RUNNABLE:就绪 + 运行(CPU 调度)
- BLOCKED:阻塞,等待 synchronized 锁
- WAITING:无限等待 wait ()
- TIMED_WAITING:限时等待 sleep ()/wait (时间)
- TERMINATED:线程执行完毕终止
七、面试高频考点总结
- start () 和 run () 区别:start () 向操作系统申请创建线程;run 只是普通任务方法,直接调用不会开启新线程。
- synchronized 锁对象:实例方法锁 this;静态方法锁类字节码对象;同步代码块锁自定义对象。
- 守护线程:setDaemon (true) 必须写在 start 之前;主线程退出,守护线程自动终止。
- wait () 和 sleep ():wait 必须在同步代码块中执行,会释放锁;sleep 不释放锁;wait 是 Object 方法,sleep 是 Thread 静态方法。
八、售票案例完整代码(同步代码块演示线程安全)
java
public class TicketTask implements Runnable{
private int ticket = 100;
private Object lock = new Object();
@Override
public void run() {
while(true){
synchronized (lock) {
if(ticket <=0){
break;
}
System.out.println(Thread.currentThread().getName()+"卖票:"+ticket);
ticket--;
}
}
}
}
//测试
public class Test{
public static void main(String[] args) {
Runnable task = new TicketTask();
Thread t1 = new Thread(task,"窗口1");
Thread t2 = new Thread(task,"窗口2");
Thread t3 = new Thread(task,"窗口3");
t1.start();
t2.start();
t3.start();
}
}