创建线程的四种方式
- 继承Thread
- 实现Runnable接口
- 实现Callable接口 + FutureTask(可以拿到返回结果, 可以处理异常)
- 线程池(工作中一般只会用到线程池)
线程池
工作中实际基本上都是使用线程池, 限制资源被无限使用而把资源消耗尽.
线程池的7大参数
- corePoolSize: 相当于创建这个线程池后, 初始化(new)了corePoolSize个线程. 它是一直存在的. 除非设置了allowCoreThreadTimeOut.
- maximumPoolSize: 线程池中最大允许的线程数量.
- keepAliveTime: 存活时间, 如果当前线程数大于核心线程数, 释放空闲的线程, 如果线程空闲时间大于存活时间. 释放的线程是(当前线程数-corePoolSize)
- TimeUnit unit: keepAliveTime的时间单位
- BlockingQueue workQueue: 阻塞队列, 如果任务很多, 就会将多的任务放在队列中, 只要有线程空闲了, 就会到队列里面取出新的任务继续执行.
- ThreadFactory threadFactory: 线程的创建工厂
- RejectedExecutionHandler handler: 队列满了异常如何处理, 按照指定的拒绝策略(抛异常, 丢弃等)来执行策略.
工作流程
- 创建线程池, 默认初始化corePoolSize个线程
- 判断核心线程是否已满, 如果满了, 就会将任务放到阻塞队列中, 空闲的core线程就会去阻塞队列中获取任务.
- 如果核心线程满了, 判断队列是否满了, 没满, 放入队列
- 队列也满了, 继续创建线程, 达到最大线程数
- 如果队列还是不够放, 采用拒绝策略.
拒绝策略
- DiscardOldestPolicy: 丢失老的任务
- CallerRunsPolicy: 直接调用任务的run方法同步调用
- AbortPolicy: 丢弃新任务, 并抛出异常
- DiscardPolicy: 丢弃新任务, 不会抛出异常
如何正确的配置线程池参数
任务的特性
- 任务性质: CPU密集型, IO密集型和混合型
- 任务的优先级: 高中低
- 任务执行的时间: 长中短
- 任务的依赖性: 是否依赖数据库等其他资源
我们可以通过Runtime.getRuntime().availableProcessors()来获取CPU的个数。
CPU密集型: 一般用CPU个数作为最大线程数, 减少CPU切换的开销. IO密集型: 最大线程数CPU个数*2
代码示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors()/2,
Runtime.getRuntime().availableProcessors(),
100,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
IntStream.rangeClosed(1, 20).forEach(value -> {
executor.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(value);
});
});
创建线程池的几种方式
- new ThreadPoolExecutor(…), 工作中常用, 可以管理线程数
- Executors.newCachedThreadPool(), 下到0, 上到Integer.MAXVALUE
- Executors.newSingleThreadExecutor(), 只会创建一个线程
- Executors.newFixedThreadPool(10), 创建corePoolSize, MaxPoolSize都是10的线程池
- Executors.newScheduledThreadPool(10), 可调度的线程池.
CompletableFuture
如果任务很复杂, 比如有些任务需要其他任务的返回结果, 有些任务和其他任务没什么关联, 可以用completableFuture来编排任务.
|