文章目录
1. 线程池
创建线程的方式
- 继承 Thread
- 实现 Runnable
- 实现 Callable
- 线程池(推荐使用线程池来创建线程)
线程池概述:
程序启动一个新的线程的成本是比较高的,因为它涉及到要与操作系统的交互. 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生命周期很短的线程时,更应该考虑使用线程池. 线程池里的每一个线程在调用结束后,并不会死亡,而是再次回到线程池中成为空闲状态,再次等待调度. 在JDK5之前,线程池必须手动来创建;JDK5之后,Java内置线程池来直接使用.
线程池的三个优点如下:
- 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁带来的消耗
- 提高响应速度: 当任务到达时,任务可以不需要等待线程创建能立即执行(线程池中存在已经创建好的线程)
- 提高线程的可管理性: 使用线程池可以统一进行线程分配、调度和监控
源码解析 Interface Executor
2. 线程池核心接口
ExecutorService 普通线程池
向线程池提交任务:
execute(Runnable command)
submit(Collable<T> task || Runnable)
ScheduledExecutoService 定时线程池
scheduleAtFixedRate()
ThreadPoolExecutor 线程池核心类
ThreadPoolExecutor : ExecutorService 的子类
ThreadPoolExecutor(int corePoolSize,intmaximumPoolSize,
long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
3. 线程池工作原理
线程池工作流程:
当一个任务提交给线程池时 ---->
核心线程池 corePoolSize
最大线程池 maximumPoolSize
阻塞队列 BlockingQueue
拒绝策略 RejectPolicy
- 首先判断核心池的线程数量是否达到 corePoolSize. 若未达到,线程池创建新的线程执行任务并将其置入核心池中; 否则,判断核心线程池是否有空闲线程. 若有,分配任务执行; 否则,进入步骤2
- 判断当前线程池中线程数量有没有达到线程池的最大数 maximumPoolSize. 若没有,创建新的线程执行任务并将其置入线程池中; 否则,进入步骤3
- 判断阻塞队列是否已满. 若未满,将任务置入阻塞队列中等待调度. 否则,进入步骤4
- 调用相应的拒绝策略打回任务.(有四种拒绝策略,默认抛出异常给用户 AbortPolicy)
4. java中常见的阻塞队列
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,静态工厂方法Executors.newFixedThreadPool()使用了这个队列
- 内置线程池 FixedThreadPool, SingleThreadPool
- SynchronousQueue:一个不存储元素的阻塞队列
- 内置线程池 CachedThreadPool (一个元素的插入必须等待同时有一个元素的删除操作,否则插入操作就一直阻塞)
- PriorityBlockingQueue: 基于优先级的阻塞队列
5. 创建线程池以及向线程池提交任务
手工创建一个线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 3, 5,
2000,TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
可以使用两个方法向线程池提交任务,分别为execute() 和 submit()方法
execute()
方法用于提交不需要返回值的任务; 所以无法判断任务是否被线程池执行成功submit()
方法用于提交需要返回值的任务; 线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
线程中提交方法 | 传入参数 |
---|---|
execute() | Runnable |
submit() | Runnable 或 Callable |
package com.ThreadPool;
import java.util.concurrent.*;
/**
* @Author: Mr.Q
* @Date: 2019-08-05 08:05
* @Description:
*/
class ThreadPoolTest implements Callable<String> {
private Integer tickets = 20;
@Override
public String call() throws Exception {
for (int i = 0; i < tickets; i++) {
if(tickets > 0)
System.out.println(Thread.currentThread().getName() +"还剩 "+tickets--+" 票...");
}
return Thread.currentThread().getName() + "票卖完了!";
}
}
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService executor = new ThreadPoolExecutor(2,3,
60,TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
ThreadPoolTest threadPoolTest = new ThreadPoolTest();
for(int i = 0; i < 5; i ++) {
executor.submit(threadPoolTest); //submit可以提交Callable,Runnable接口
}
executor.shutdown();
}
}
提交了5次任务,但是 ThreadPool 创建了两个线程;
原因是核心线程池未满,有空闲线程;由于实现 Callable接口只做了 System.out
,线程拿到任务很快执行完成;执行任务的速度快于提交任务的速度,就不在创建线程了…
线程池中的线程被包装为Worker工作线程,具备可重复执行任务的能力
6. 合理配置线程池
查看当前电脑CPU数目:
System.out.println(Runtime.getRuntime().availableProcessors());
输出的为当前电脑可执行的线程数目,目前默认一个CPU可以同时执行俩个线程,CPU数为线程数的一半
配置核心池以及最大线程池线程数量
- CPU密集型任务:nCPU + 1
- IO密集型任务: 2 * nCPU
CPU密集型任务 : 频繁操作CPU(大数运算)
IO密集型任务 : 频繁操作文件(读写文件)
7. JDK 内置的四大线程池 Executor
Executor 框架最核心的类是ThreadPoolExecutor,它是线程池的实现类。通过Executor框架的工具类Executors,可以创建3种类型的 ThreadPoolExecutor
1.缓存线程池(无大小限制的线程池):newCachedThreadPool()
使用场景:适用于负载较轻的服务器,或执行很多短期的异步任务
ExecutorService executorService = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
任务提交速度 > 线程执行速度 | 会不断创建线程(有可能无限创建线程将内存写满) |
---|---|
任务提交速度 < 线程执行速度 | 只会创建若干线程 |
2.固定大小线程池:newFixedThreadPool(int nThreads)
使用场景: 适用于负载较重的服务器,来满足资源分配的要求
ExecutorService executorService = Executors.newFixedThreadPool();
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3.单线程池:newSingleThreadExecutor()
使用场景: 多线程场景下需要让任务串行执行
ExecutorService executorService = Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
4.定时调度池: newScheduledThreadPool();
使用场景:ScheduledExecutorService是java.util.concurrent并发包下的一个接口,表示调度服务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool();
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
定时调度池中的方法
scheduled.schedule();
延迟 delay个时间单元后创建 nThreads个线程执行 command任务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool();
scheduled.schedule();
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
scheduled.scheduleAtFixedRate();
延迟 delay个时间单元后每隔 period time执行一次 command任务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(3);
scheduled.scheduleAtFixedRate();
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
scheduled.scheduleWithFixedDelay();
scheduled.scheduleAtFixedRate();
和 scheduled.scheduleWithFixedDelay();
都表示每间隔一段时间定时执行任务
- scheduleAtFixedRate是以上一次任务的开始时间为间隔的,并且当任务执行时间大于设置的间隔时间时,真正间隔的时间由任务执行时间为准
- scheduleWithFixedDelay是以上一次任务的结束时间为间隔
ScheduledExecutorService中方法详解(参考)