线程池知识小结
Posted 赵jc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程池知识小结相关的知识,希望对你有一定的参考价值。
线程池
为什么要有线程池?
- 单个线程的创建时它会开辟本地方法栈、虚拟机栈、程序计数器成为线程的私有内存,因此频繁的创建和消耗会耗费系统的资源
- 在任务量远远大于线程可以处理的任务量时,并不能友好的拒绝任务。
基于上面的问题我们引入了线程池(采用池化技术来管理和使用线程),线程池相对于线程来说是长生命周期的。线程池有两个重要的角色是线和任务队列。
线程池的优点
线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
合理使用线程池能够带来的以下好处:
- 降低资源消耗,减少了创建和销毁线程的次数(每个工作线程都可以被重复利用,可执行多个任务)
- 提高响应速度,当任务到达时,任务可以不需要等线程创建就能立即执行
- 可以更好的管理线程的数量和资源的个数
- 线程池可以执行定时任务和拒绝不能处理的任务
线程池的执行流程
线程池的执行方式
- 1.执行的任务无返回值
execute
(new Runnable)execute执行任务如果有OOM异常会将异常打印到控制台 - 2.执行的的任务有返回值
submit
(new Runnable 无返回值/ new Callable 有返回值)execute执行任务如果有OOM异常不会将异常打印到控制台
线程池的关闭
shutdown
:拒绝新任务加入,等待线程池中的任务队列执行完之后在停止线程池shutdownNow
:拒绝执行新任务,并且会立即停止,不会等待任务队列中的任务执行完,才停止线程池
线程池的状态
线程池共有五种状态。要与线程的状态分开哦。
- RUNNING:这个没什么好说的,这是最正常的状态:接受新的任务,处理等待队列中的任务;
- SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务;
- STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执⾏任务的线程;
- TIDYING:所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执行钩子⽅法terminated();
- TERMINATED:terminated() ⽅法结束后,线程池的状态就会变成这个。
线程池的创建
Java里面线程池的顶级接口是 java.util.concurrent.Executor (JUC),但是严格意义上讲 Executor 并不是一个线程 池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService 。
ThreadPoolExecutor创建
这种是最经典的线程池创建方式,也时最常用的方式,这种方式可以
优点
- 这种方式可以解决线程数量不可控的问题
- 这种方式可以解决任务数量不可控的问题
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5,//核心线程数--->正式员工
10,//最大线程数--->正式员工+临时工
60,
TimeUnit.SECONDS,//idle线程的空闲时间:临时工最大的存活时间,超过时间就解雇
new LinkedBlockingQueue<>(),//阻塞队列:任务存放的地方(快递仓库)
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {//线程池中定义的任务类r
return new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行了");
//r对象是线程池内部封装过的工作任务类(Worker),会一直循环等待的方式从阻塞队列中取任务来执行
// r.run();
}
});
}
},//创建线程的工厂类:线程池创建线程时,调用该工厂的方法创建线程--->招聘员工的标准
new ThreadPoolExecutor.AbortPolicy()
/**
* 拒绝策略:达到最大线程数且阻塞队列已满,采取的拒绝策略
* AbortPolicy:直接抛RejectedExecutionException(不提供handler时的默认策略)
* CallerRunsPolicy:谁(某个线程)交给我(线程池)任务,我拒绝执行,由谁自己执行
* DiscardPolicy:交给我的任务,直接丢弃掉(尾删)
* DiscardOldestPolicy:丢弃阻塞队列中最旧的任务(头删)
*/
);
}
}
7个参数:
- corePoolSIze:所谓的核⼼线程数,可以⼤致理解为⻓期驻留的线程数⽬
- maximumPoolSize:顾名思义,就是线程不够时能够创建的最⼤线程数。
- keepAliveTime:空闲线程的保活时间,如果线程的空闲时间超过这个值,那么将会被关闭。注意此值 ⽣效条件必须满⾜:空闲时间超过这个值,并且线程池中的线程数少于等于核⼼线程数
- TimeUnit:保活时间单位。
- BlockingQueue:任务丢列,⽤于存储线程池的待执⾏任务的。
- threadFactory:⽤于⽣成线程,⼀般我们可以⽤默认的就可以了。或者自定义我们需要的任务。
- handler:拒绝策略,当线程池已经满了,但是⼜有新的任务提交的时候,该采取什么策略由这个来指定。有⼏种 ⽅式可供选择,像抛出异常、直接拒绝然后返回等,也可以⾃⼰实现相应的接⼝实现⾃⼰的逻辑。
5种拒绝策略
拒绝策略:达到最大线程数且阻塞队列已满,采取的拒绝策略
- AbortPolicy:直接抛RejectedExecutionException(不提供handler时的默认策略)
- CallerRunsPolicy:谁(某个线程)交给我(线程池)任务,我拒绝执行,由谁自己执行
- DiscardPolicy:交给我的任务,直接丢弃掉(尾删)
- DiscardOldestPolicy:丢弃阻塞队列中最旧的任务(头删)
- 我们自定的拒绝策略,可以写进日志里或者存储到数据库当中
下面介绍的方法都不推荐,可能会造成内存溢出(OOM)的问题,在JAVA开发手册中明确规定了不允许
FixedThreadPool创建
创建一个固定个数的线程池
ExecutorService executorService =
Executors.newFixedThreadPool(10);
SingleThreadExecutor
创建单个线程的线程池,上一种创建线程的单机版本
ExecutorService service = Executors.newSingleThreadExecutor();
创建单个线程池有什么用呢?·
- 可以避免频繁创建和销毁线程带来的性能开销
- 有任务队列是可以存储多余的任务
- 当任务量过大时 可以有好的拒绝
- 线程池可以更好的管理任务
CachedThreadPoo
创建带缓存的线程池,适用于短期有大量任务的时候。
ExecutorService executorService =
Executors.newCachedThreadPool();
FixedThreadPool(10, threadFactory)
自定义规则的线程池
public static void main(String[] args) {
// 自定义线程工厂
MyThreadFactory threadFactory = new MyThreadFactory();
ExecutorService executorService =
Executors.newFixedThreadPool(10, threadFactory);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
System.out.println("线程名:" +
thread.getName() +
",优先级:" + thread.getPriority());
}
});
}
}
private static int count = 1;
static class MyThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
// 自定义线程池的名称规则
thread.setName("mythreadpool-" + count++);
// 设置优先级
thread.setPriority(10);
return thread;
}
}
ScheduledThreadPool
创建执行定时任务的线程池必须传递一个参数
ublic static void main(String[] args) {
// 创建执行定时任务的线程池
//必须要传递一个参数
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(1);
System.out.println("设置定时任务:" + new Date());
// 执行定时任务
//参数 1任务 2延迟多少时间执行任务 4时间的单位
//只会执行一次
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:" + new Date());
}
}, 1, TimeUnit.SECONDS);
//参数 1任务 2延迟多少时间执行任务 3执行的频率 4时间的单位
//以上次任务的开始时间就开始计时,作为计算下一次任务的开始时间
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:" + new Date());
}
}, 1, 3, TimeUnit.SECONDS);
//参数 1任务 2延迟多少时间执行任务 3执行的频率 4时间的单位
//以上次任务的结束时间开始计时,作为下次任务的开始时间
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:" + new Date());
}
}, 1, 3, TimeUnit.SECONDS);
}
SingleThreadScheduledExecutor
创建单个执行定时任务的线程池,上一种创建方式的单机版本
ScheduledExecutorService service =
Executors.newSingleThreadScheduledExecutor();
newWorkStealingPool
JDK8之后会根据当前CPU生成对应个数的线程池,并且是异步执行的
因为是异步,所以创建出来便结束
public static void main(String[] args) {
// 创建一个异步根据当前CPU生产的线程池
ExecutorService service = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" +
Thread.currentThread().getName());
}
});
}
// 等待线程池执行完成
while (!service.isTerminated()) {
}
}
以上是关于线程池知识小结的主要内容,如果未能解决你的问题,请参考以下文章
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段