面试题答案
一键面试1. 工作原理
- 可缓存线程池(CachedThreadPool):
- 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
- 如果线程池里所有线程都在执行任务,又有新任务到来,会创建新线程处理任务。
- 线程池中的线程如果60秒内未被使用就会被回收。
- FixedThreadPool:
- 线程池大小固定,在创建后,线程数量不会改变。
- 当有新任务提交时,如果线程池中有空闲线程,则立即执行任务;如果没有空闲线程,任务会被放入阻塞队列等待。
- SingleThreadExecutor:
- 只有一个线程的线程池,它确保所有任务按照提交顺序依次执行。
- 所有任务会被放入一个队列中,由这唯一的线程按顺序逐个执行。
2. 资源管理
- 可缓存线程池(CachedThreadPool):
- 资源消耗不固定,可能会在短时间内创建大量线程,从而消耗过多内存等资源,如果任务执行时间较长,会导致线程数量持续增长,可能引发OOM(OutOfMemoryError)。
- 适用于处理大量短时间任务,线程复用率高,在任务执行完后,空闲线程会被回收,释放资源。
- FixedThreadPool:
- 资源消耗相对稳定,因为线程数量固定。
- 线程创建后一直存在,即使无任务执行也不会销毁,占用一定系统资源。
- SingleThreadExecutor:
- 资源消耗最少,只有一个线程在运行,占用的线程资源恒定。
- 所有任务排队等待执行,不会因为线程过多而消耗过多资源。
3. 适用场景
- 可缓存线程池(CachedThreadPool):
- 适用于执行大量的短时间异步任务,例如网页爬虫场景,需要快速发起大量HTTP请求获取网页数据,每个请求任务执行时间较短,CachedThreadPool能快速创建和复用线程,提高效率。
- 不适合长时间运行的任务,否则线程不断创建可能耗尽资源。
- FixedThreadPool:
- 适用于处理负载相对稳定,且任务执行时间较长的场景。例如在电商系统中,订单处理任务,需要进行一系列复杂的业务逻辑处理,且处理频率相对稳定,使用FixedThreadPool能保证系统资源的合理利用,避免过多线程创建销毁带来的开销。
- 可控制并发度,防止因并发过高导致系统资源耗尽。
- SingleThreadExecutor:
- 适用于需要顺序执行任务,且不希望有并发干扰的场景。比如数据库的事务操作,要求按顺序执行,避免并发操作导致数据一致性问题,使用SingleThreadExecutor可以保证任务顺序执行。
4. 业务需求下的选择示例
- 需求一:处理大量短时间的异步任务
- 比如图片的缩略图生成,当用户上传图片后,系统需要快速生成不同尺寸的缩略图。每个缩略图生成任务执行时间较短,但数量可能较多。
ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 100; i++) { executorService.submit(() -> { // 生成缩略图的业务逻辑 System.out.println("生成缩略图任务 " + Thread.currentThread().getName()); }); } executorService.shutdown();
- 需求二:处理负载稳定且执行时间长的任务
- 假设是一个视频转码系统,接收到用户上传的视频后,需要将其转码为不同格式。转码任务执行时间较长且系统接收任务频率相对稳定。
ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executorService.submit(() -> { // 视频转码的业务逻辑 System.out.println("视频转码任务 " + Thread.currentThread().getName()); }); } executorService.shutdown();
- 需求三:需要顺序执行且无并发干扰的任务
- 以银行转账操作为例,需要保证每笔转账操作按顺序依次执行,防止并发导致账户余额计算错误。
ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++) { executorService.submit(() -> { // 银行转账的业务逻辑 System.out.println("银行转账任务 " + Thread.currentThread().getName()); }); } executorService.shutdown();