在讲线程池之前,我们先来说说线程。
线程分为单线程和多线程;单线程意味着一个线程正在执行任务。多线程意味着创建多个线程同时执行任务。
比如我们使用浏览器浏览网页,如果一次只能打开一个窗口,这就是单线程;现在我们的浏览器肯定可以打开多个窗口,比如我们可以在一个窗口中听音乐,其他窗口也可以看新闻,这就是多线程的概念。并行和并发也是同一概念。例如,如果你正在开车,然后有朋友打电话,并行性:开车时,你使用蓝牙耳机接听电话,同时处理;并发:停在路边,接电话,接电话后继续行驶。
创建线程需要资源和时间。如果任务来了才创建线程,响应时间会变长。并且一个进程可以创建的线程数量是有限的。
为了避免这些问题,在程序启动时会创建几个线程来响应处理。它们被称为线程池,里面的线程被称为工作线程。
java API提供了Executor框架来创建不同的线程池。
避免频繁创建和销毁线程,实现线程对象的复用;线程资源管理
JAVA中创建线程池主要有两种方法。一种是Executors工厂类提供的方法,它提供了4种不同的线程池供使用。另一个类是通过 ThreadPoolExecutor 类创建的。
创建可缓存的线程池。如果线程数超过处理需求,一段时间后就会回收缓存。如果线程数量不够,则会创建新线程。
公共 静态 空 main(字符串[]参数) {
ExecutorService executorService=执行器.newCachedThreadPool() );
对于(int我= 0;我<10;++) {
int最终I=i;
executorService.执行(()-> {
系统.out.println(LocalDateTime .现在()+" " +线程。当前线程()。 getName()+" "+最终I);
尝试{
线程.睡眠(3000);
} catch (InterruptedException) e) {e.printStackTrace();
}
});
}
}
2021-06-16T10:01:27.728池 -1-螺纹-10
2021-06-16T10:01:27.728池-1-螺纹-3 2
2021-06-16T10:01:27.728池-1-螺纹-2 1
2021-06-16T10:01:27.728池-1-螺纹-4 3
2021-06-16T10:01:27.728池-1-螺纹-5 4
2021-06-16T10:01:27.728池-1-螺纹-6 5
2021-06-16T10:01:27.728池-1-螺纹-10 9
2021-06-16T10:01:27.728池-1-螺纹-8 72021-06-16T10:01:27.728池-1-螺纹-7 6
2021-06-16T10:01:27.728池-1-螺纹-9 8
初始线程池没有线程,如果线程不足,就会不断创建新线程,所以线程名称都不同
创建固定大小的线程池,控制并发线程数,多余的线程会在队列中等待。
公共 静态 空 main(字符串[]参数) {
ExecutorService executorService =执行器.newFixedThreadPool(3);
对于(int我= 0; i < 10; i ++){
int最终I=i;
executorService.执行(() -> {
//获取线程名称,默认格式:pool-1-thread-1系统.out.println(LocalDateTime .现在()+" " +线程。当前线程()。 getName()+" "+最终I);
尝试{
线程.睡眠(3000);
} catch (InterruptedException) e) {
e.printStackTrace();
}
});
}
}
2021-06-16T10:12:36.436池 -1-螺纹-21
2021-06-16T10:12:36.436池-1-螺纹-3 2
2021-06-16T10:12:36.436池-1-螺纹-1 0
2021-06-16T10:12:39.447池-1-螺纹-1 32021-06-16T10:12:39.447池-1-螺纹-2 5
2021-06-16T10:12:39.447池-1-螺纹-3 4
2021-06-16T10:12:42.452池-1-螺纹-2 6
2021-06-16T10:12:42.452池-1-螺纹-17
2021-06-16T10:12:42.452池-1-螺纹-3 8
2021-06-16T10:12:45.460池-1-螺纹-1 9
因为线程池大小是固定的,这里设置的是3个线程,所以线程名只有3个。线程因为线程不足会进入队列等待线程空闲,所以日志间隔3秒输出
创建一个循环的线程池,支持定时及循环执行任务。
公共 静态 空 main(字符串[]参数) {ScheduledExecutorService executorService =执行器.newScheduledThreadPool() );
对于(int我= 0; i < 10; i ++){
int最终I=i;
executorService.时间表(() -> {
//获取线程名称,默认格式:pool-1-thread-1
系统.out.println(本地日期时间 .现在()+" “+线程。当前线程()。 getName()+" " +最终I);
尝试{
线程.睡眠(3000);
} catch (InterruptedException) e) {
e.printStackTrace();
}}、3、时间单位。秒 );
}
}
2021-06-16T10:16:41.187池 -1-螺纹-21
2021-06-16T10:16:41.187池-1-螺纹-1 0
2021-06-16T10:16:41.187池-1-螺纹-3 2
2021-06-16T10:16:44.200池-1-螺纹-1 3
2021-06-16T10:16:44.200池-1-螺纹-3 4
2021-06-16T10:16:44.200池-1-螺纹-2 5
2021-06-16T10:16:47.221池-1-螺纹-1 6
2021-06-16T10:16:47.221池-1-螺纹-2 72021-06-16T10:16:47.221池-1-螺纹-3 8
2021-06-16T10:16:50.236池-1-螺纹-3 9
延迟设置为3秒,所以任务提交后3秒才会开始执行。因为这里设置了核心线程数为3,不足的线程会进入队列等待空闲线程,所以每3秒输出一次日志。
注意:这里使用的是ScheduledExecutorService类的schedule()方法,而不是ExecutorService类的execute()方法。
创建单线程线程池,保证所有任务按照指定的顺序(先进先出、后进先出、优先级)执行。
公共 静态 空 main(字符串[]参数) {
ExecutorService executorService =执行器.newSingleThreadExecutor( );
对于(int我= 0; i < 10; i ++){
int最终I=i;executorService.执行(() -> {
//获取线程名称,默认格式:pool-1-thread-1
系统.out.println(本地日期时间 .现在()+" “+线程。当前线程()。 getName()+" " +最终I);
尝试{
线程.睡眠(3000);
} catch (InterruptedException) e) {
e.printStackTrace();
}
});
}
}
2021-06-16T10:19:20.117池 -1-螺纹-10
2021-06-16T10:19:23.122池-1-螺纹-112021-06-16T10:19:26.132池-1-螺纹-1 2
2021-06-16T10:19:29.147池-1-螺纹-1 3
2021-06-16T10:19:32.147池-1-螺纹-14
2021-06-16T10:19:35.152池-1-螺纹-15
2021-06-16T10:19:38.155池-1-螺纹-16
2021-06-16T10:19:41.165池-1-螺纹-17
2021-06-16T10:19:44.166池-1-螺纹-18
2021-06-16T10:19:47.180池-1-螺纹-1 9
只有一个线程,所以线程名均相同,且是每隔3秒按顺序输出的
霹雳类提供了4种构造方法,可以根据需要来自定义。
当线程数小于corePoolSize时,创建一个线程;
当线程数大于等于corePoolSize且任务队列未满时,将任务放入任务队列中;
当线程数大于等于corePoolSize时,任务队列已满;
如果线程数小于最大线程数,则创建线程;
如果线程数等于最大线程数,则抛出异常,任务被拒绝。
公共ThreadPoolExecutor(int corePoolSize ,
int最大池大小,
长 keepAliveTime,
TimeUnit 单位,
BlockingQueue<可运行>workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler 处理程序) {
//省略
}
可以看到有7个参数:
1) corePoolSize 核心线程数,线程池中始终存活的线程数
2)maximumPoolSize最大线程数,线程池允许的最大线程数
3)keepAliveTime生存时间,在没有任务执行的情况下,线程可以持续多久直到终止
4)单位参数keepAliveTime时间单位
TimeUnit.DAYS 天
TimeUnit.HOURS 小时
TimeUnit.MINUTES 分钟
TimeUnit.SECONDS 秒
TimeUnit.MILLISECONDS 毫秒TimeUnit.微秒微妙
时间单位.NANOSECONDS 纳秒
5)workQueue阻塞队列,用于存放等待执行的任务,全部线程安全
ArrayBlockingQueue 由数组结构组成的有界阻塞队列。
LinkedBlockingQueue 由链表结构组成的有界阻塞队列。
SynchronousQueue 不存储元素的阻塞队列,即将元素直接提交给线程,不保留元素。
PriorityBlockingQueue 支持优先级排序的无界阻塞队列。
DelayQueue 使用优先级队列实现的无界阻塞队列,只有在延迟到期时才能从中提取元素。
LinkedTransferQueue 由链表结构组成的无界阻塞队列。与SynchronousQueue类似,它也包含非阻塞方法。
LinkedBlockingDeque 由链表结构组成的双向阻塞队列。
常用的有LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
6)threadFactory线程工厂,主要用于创建线程,默认为普通优先级,非守护线程
7)handler拒绝策略,拒绝任务的策略。默认为 AbortPolicy。
AbortPolicy 拒绝并抛出异常。
CallerRunsPolicy 重试提交当前任务,即再次调用 execute()方法运行任务。
DiscardOldestPolicy 丢弃队列头部(最旧的)任务并执行当前任务。
DiscardPolicy 丢弃当前任务。
公共 静态 空 螺纹5() {ExecutorService executorService = new ( 2、 10、 1、 时间单位。 微秒、 新 ArrayBlockingQueue <>(5,真));
对于(int我= 0;我<10;++) {
int索引=i;
executorService.执行(()-> {
系统.out.println(LocalDateTime .现在()+ " " +线程。当前线程().getName()+》《+》 索引);
尝试{
线程.睡眠(2000);} catch (InterruptedException) e) {
e.printStackTrace();
}
});
}
}
2021-06-16T15:32:41.801池-1-螺纹-2 1
2021-06-16T15:32:41.801泳池-1-线程-3 7
2021-06-16T15:32:41.801泳池-1-线程-5 9
2021-06-16T15:32:41.801泳池-1-线程-10
2021-06-16T15:32:41.801泳池-1-线程-48
2021-06-16T15:32:43.817池-1-线程-3 2
2021-06-16T15:32:43.818泳池-1-线程-432021-06-16T15:32:43.818泳池-1-线程-2 4
2021-06-16T15:32:43.818泳池-1-线程-55
2021-06-16T15:32:43.818池-1-线程-16
核心线程数为2,阻塞队列为5,生存时间为1分钟。所以任务流程为: