线程池
使用原始方式创建线程
继承Thread类
public static void main(String[] args) {
System.out.println("main-----start-----");
new User().start();
System.out.println("main-----end-------");
}
private static class User extends Thread{
@Override
public void run() {
System.out.println("使用继承Thread方式开启线程");
}
}
实现Runnable接口
public static void main(String[] args) {
System.out.println("main-----start-----");
User user= new User();
new Thread(user).start();
System.out.println("main-----end-------");
}
private static class User implements Runnable{
@Override
public void run() {
System.out.println("使用实现Runnable接口开启线程");
}
}
实现Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main-----start-----");
FutureTask<Integer> integerFutureTask = new FutureTask<>(new User());
new Thread(integerFutureTask).start();
Integer integer = integerFutureTask.get();
System.out.println(integer);
System.out.println("main-----end-------");
}
private static class User implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("实现Callable接口创建线程");
return 10;
}
}
总结:以上三种开启线程的方式在程序中都不推荐使用,如果这样创建线程就好比公司有活了就招一个人,没活了就开除,之后有活了又招i 一个人,显然是不合理的,如果有100万请求同时访问一个接口,这个接口一次又会开一个线程跑程序,就是有100万个线程同时进行。这时候我们就需要使用线程池
线程池
使用线程池的优点
- 统一管理资源
- 更快速的执行任务,不需要每次创建线程销毁线程消耗时间,免去了系统资源的开销
- 如果不使用线程池,有可能会创建大量的线程导致消耗完系统内存
四大线程池
- Executors.newFixedThreadPool(size)
创建一个固定大小的线程池,核心线程与最大线程与设置的一致,阻塞队列的大小是Integer的最大值 - Executors.newSingleThreadExecutor()
创建只有一个线程的线程池,核心线程与最大线程都是1,阻塞队列的大小是Integer的最大值 - Executors.newCachedThreadPool()
创建一个有缓存的线程池,没有核心线程,最大线程为Integer的最大值,线程空闲60s自动销毁 - Executors.newScheduledThreadPool(10)
创建一个定长线程池,支持定时及周期性任务执行
自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10,
20,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
参数一(corePoolSize): 线程池的核心线程,表示就算空闲也不会被销毁,在第一次使用使用线程池创建
参数一(maximumPoolSize): 线程池的最大线程,表示还可以创建maximumPoolSize-corePoolSize的线程数量
参数三(keepAliveTime): maximumPoolSize-corePoolSize的线程空闲多久销毁线程
参数四(unit): 时间单位
参数五(workQueue): 阻塞队列,线程满了之后任务到队列中等待
参数六(threadFactory) : 线程的创建工厂
参数七(handler): 当核心线程满了,队列中也满了,最大线程也满了,将使用拒绝策略拒绝任务
拒绝策略:
1. AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException
2. CallerRunsPolicy:直接在 execute 方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被丢弃;
3. DiscardPolicy:直接丢弃任务
4. DiscardOldestPolicy:丢弃队列中等待最长的任务,执行当前任务,如果线程池已经关闭,任务将被丢弃
工作顺序
当有任务时,首先会使用核心线程去执行,核心线程满了之后任务会进去阻塞队列中等待,当阻塞队列也满了之后,将会开启线程执行任务,当任务空闲指定之间之后将会被销毁,如果最大线程也满了之后则会使用拒绝策略拒绝任务
例:核心线程:3,最大线程20,阻塞队列大小:50。 现在有100个任务怎么分配?
首先会让3个核心线程执行3个任务,50个任务进入阻塞队列,再创建17个线程执行任务,拒绝30个任务(不考虑执行完成之后再取任务)
推荐使用
阿里巴巴规范手册中禁止使用Executors去创建线程池
原因:newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM
newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
推荐使用ThreadPoolExecutor创建线程池
例:
@Bean("asyncExecutor")
public Executor asyncServiceExecutor() {
log.info("start asyncServiceExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(5);
//配置最大线程数
executor.setMaxPoolSize(5);
//配置队列大小
executor.setQueueCapacity(99999);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-service-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}