多线程回顾
多线程实现的4种方式
1. 继承 Thread
类
通过继承 Thread
类并重写 run()
方法实现多线程。
public class MyThread extends Thread {@Overridepublic void run() {System.out.println("线程运行: " + Thread.currentThread().getName());}
}// 使用
public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程
}
特点:
- 缺点:Java 单继承的限制,无法再继承其他类。
- 适用场景:简单任务,无需共享资源。
2. 实现 Runnable
接口
实现 Runnable
接口,将任务逻辑写在 run()
方法中。
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("线程运行: " + Thread.currentThread().getName());}
}// 使用
public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();
}
特点:
- 优点:避免单继承限制,适合资源共享(如多个线程处理同一任务)。
- 推荐场景:大多数情况下优先使用。
3. 实现 Callable
接口 + Future
通过 Callable
允许返回结果和抛出异常,结合 Future
或 FutureTask
获取异步结果。
import java.util.concurrent.*;public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "执行结果: " + Thread.currentThread().getName();}
}// 使用
public static void main(String[] args) throws Exception {ExecutorService executor = Executors.newSingleThreadExecutor();Future<String> future = executor.submit(new MyCallable());System.out.println(future.get()); // 阻塞获取结果executor.shutdown();
}
特点:
- 优点:支持返回值和异常处理。
- 适用场景:需要获取线程执行结果的场景。
4. 使用线程池(Executor
框架)
通过 Executors
工具类创建线程池,统一管理线程资源。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDemo {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);for (int i = 0; i < 5; i++) {executor.execute(() -> {System.out.println("线程运行: " + Thread.currentThread().getName());});}executor.shutdown();}
}
特点:
- 优点:降低资源消耗,提高线程复用率,支持任务队列和拒绝策略。
- 推荐场景:生产环境首选,高并发任务处理。
对比与建议
方式 | 返回值 | 异常处理 | 灵活性 | 资源消耗 |
---|---|---|---|---|
继承 Thread | 不支持 | 有限 | 低 | 高 |
实现 Runnable | 不支持 | 有限 | 高 | 低 |
实现 Callable | 支持 | 支持 | 高 | 中 |
线程池(Executor ) | 支持 | 支持 | 最高 | 最低 |
建议:
- 优先选择 实现
Runnable
/Callable
接口,避免继承局限性。 - 生产环境务必使用 线程池,提升性能并确保稳定性。
- 需要结果时使用
Callable
+Future
,简单任务用Runnable
。
线程池ExecutorService的7大参数
线程池构造函数
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler
)
参数说明
1. 核心线程数(corePoolSize)
- 作用:线程池中始终保持存活的线程数量(即使空闲)。
- 特点:
- 默认情况下,核心线程在空闲时不会销毁(除非设置
allowCoreThreadTimeOut(true)
)。
- 默认情况下,核心线程在空闲时不会销毁(除非设置
2. 最大线程数(maximumPoolSize)
- 作用:线程池允许创建的最大线程数(包括核心线程和非核心线程)。
- 规则:
- 当任务队列已满且当前线程数小于最大线程数时,会创建新线程处理任务。
3. 线程存活时间(keepAliveTime + unit)
- 作用:非核心线程空闲时的存活时间。
unit
为时间单位 - 规则:
- 非核心线程在空闲时间超过
keepAliveTime
后会被销毁。 - 如果
allowCoreThreadTimeOut(true)
,核心线程也会受此时间限制。
- 非核心线程在空闲时间超过
4. 任务队列(workQueue)
- 作用:用于存放待执行任务的阻塞队列。
- 常见队列类型:
- 无界队列:如
LinkedBlockingQueue
(默认无界,可能导致 OOM)。 - 有界队列:如
ArrayBlockingQueue
(需指定容量)。 - 同步移交队列:如
SynchronousQueue
(不存储任务,直接移交线程)。
- 无界队列:如
5. 线程工厂(threadFactory)
- 作用:自定义线程的创建方式(如命名、优先级、是否为守护线程等)。
- 默认实现:
Executors.defaultThreadFactory()
。
6. 拒绝策略(handler)
- 作用:当任务队列已满且线程数达到最大时,如何处理新提交的任务。
- 常见策略:
AbortPolicy
(默认):抛出RejectedExecutionException
异常,且不会静默丢弃任务CallerRunsPolicy
:由提交任务的线程直接执行任务。DiscardPolicy
:静默丢弃新任务。DiscardOldestPolicy
:丢弃队列中最旧的任务,尝试重新提交新任务。
运行流程
1. 线程池创建,准备好 core 数量的核心线程,准备接受任务2. 新的任务进来,用 core 准备好的空闲线程执行。(1)、core满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队列获取任务执行(2)、阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量(3)、max都执行好了。Max-core 数量空闲的线程会在 keepAliveTime指定的时间后自动销毁。最终保持到 core 大小(4)、如果线程数开到了 max的数量,还有新任务进来,就会使用 reject 指定的拒绝策略进行处理3. 所有的线程创建都是由指定的 factory 创建的。
- 优先级顺序:核心线程 → 任务队列 → 非核心线程 → 拒绝策略。
- 非核心线程:仅在队列满时创建,空闲超时后销毁。
- 队列选择:
- 无界队列:可能导致 OOM(如
LinkedBlockingQueue
)。 - 同步队列:适合高并发快速响应(如
SynchronousQueue
)。
- 无界队列:可能导致 OOM(如
常见面试问题:
一个线程池中core 7; max 20; quue 50, 100个并发进来怎么分配:
7个会被立即执行,50个进入阻塞队列,再开13个线程进行执行,剩下的30个就使用拒绝策略
常见4种线程池
A、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
core是0,所有都可回收
B、newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
固定大小,core=max;都不可回收
C、newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
定时任务的线程池
D、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一