Java线程池学习笔记
1.线程池
- 线程的创建和销毁需要一定的开销;
- 需要线程池对线程进行管理,减少资源的耗费;
- Executor框架将任务的创建和执行进行解耦:Runnable和Callable进行任务的创建,ThreadPoolExecutor用于执行任务;
1.1.Executor
execute和submit
- execute适用于"触发就忘"的场景,submit适合需要获取结果或控制任务状态的场景;
- submit的返回值是Future,可以通过Future检查任务是否完成,取消任务, 获取阻塞等待结果,执行到异常时不会立即抛出,在get方法会抛出异常
2.ThreadPoolExecutor
2.1.构造方法
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
-
corePoolSize:核心线程数;调用prestartAllcoreThread会启动所有核心线程;
-
maximumPoolSize:最大线程数;
-
keepAliveTime:非核心线程超时回收时间;调用allowCoreThreadTimeout为true,超时回收也会应用到核心线程上;
-
unit:上面参数的时间单位;
-
workQueue:任务队列;
-
threadFactory:线程工厂;作用:给创建的线程设置名字,代码:
java// 创建线程工厂,用于给线程命名 ThreadFactory threadFactory = new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "MyThread-" + threadNumber.getAndIncrement()); return t; } }; -
RejectedExecutionHandler:饱和策略;当任务队列和线程数都满了时执行的策略,默认AbortPolicy:无法处理新任务,并抛出RejectedExecutionException异常;此外还有三种策略:
- CallerRunsPolicy:用调用者所在线程执行任务;
- DiscardPolicy:丢弃待执行的任务;
- DiscardOldestPolicy:丢弃队列中最早进入队列的任务(队列的头),执行当前任务
2.2.线程池执行顺序
- 启动核心线程
- 核心线程满了,加入任务队列
- 任务队列满了,启动非核心线程,数量不能超过最大线程数-核心线程数
- 执行饱和策略
3.线程池种类
3.1.Executors.newFixedThreadPool
- 实现:new ThreadPoolExecutor(n,n,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())
- 工作流程:启动核心线程,核心线程满了加入任务队列,核心线程运行完取出任务执行
- 没有超时回收,因为没有非核心线程
3.2.Executors.newCachedThreadPool
- 实现:new ThreadPoolExecutor(0,Integer.MAX_VALUE,60,TimeUnit.SECONDS,new SynchronousQueue())
- 工作流程:加入任务队列,SynchronousQueue不支持存放元素,存储一个任务就需要移除它,所以要创建一个非核心线程取出任务进行执行,执行任务时如果有空闲线程就不创建新线程,反之需要创建
- 线程执行完任务空闲60s没有任务过来超时回收
3.3.Executors.newSingleThreadExecutor
- 实现:new ThreadPoolExecutor(1,1,0,TimeUnit.MILLISECONDS,new LinkedBlocking())
- 工作流程:创建核心线程,添加到任务队列,取出任务队列执行
- 特点:单个线程按顺序执行任务
3.4.Executors.newScheduledThreadPool
-
实现:new ThreadPoolExecutor(n,Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLISSECONDS,TimeUnit.MILLISECONDS,new DelayedWorkQueue())
-
工作流程:创建核心线程,放入队列等待核心线程空闲,因为DelayedWorkQueue无界,基本不会创建非核心线程
-
常用API:
- schedule:延时执行
javaScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); // 场景1:3秒后发送一条通知 scheduler.schedule(() -> { System.out.println("3秒到了,发送推送通知"); }, 3, TimeUnit.SECONDS);- scheduleAtFixedRate:固定频率执行
java// 场景2:每5秒检查一次网络状态(不管任务执行多久) scheduler.scheduleAtFixedRate(() -> { System.out.println("检查网络连接状态..."); // 假设这个检查需要2秒 }, 0, 5, TimeUnit.SECONDS); // 参数:首次延迟0秒,之后每5秒执行一次- scheduleWithFixedDelay:固定延迟执行
java// 场景3:每次请求完成后,等待3秒再进行下一次 scheduler.scheduleWithFixedDelay(() -> { System.out.println("拉取服务器数据..."); // 假设拉取数据需要2秒 Thread.sleep(2000); }, 0, 3, TimeUnit.SECONDS); // 执行流程:拉取(2秒) -> 等待3秒 -> 拉取(2秒) -> 等待3秒...
4.线程池和其他API比较
4.1.Android开发很少用Executors的原因
-
生命周期管理复杂
java// 容易忘记shutdown,导致内存泄漏 // 需要在每个Activity的onDestroy中处理,容易遗漏 -
有更好的替代方案
替代方案 优势
Kotlin Coroutines 自动绑定生命周期,Scope管理
RxJava 丰富的操作符,自动取消订阅
WorkManager 系统级任务调度,保证执行
LiveData + Room 数据库异步查询自动管理