1、使用线程池
1.1、什么是线程池
简而言之,线程池就是管理线程的一个容器,有任务需要处理时,会相继判断核心线程数是否还有空闲、线程池中的任务队列是否已满、是否超过线程池大小,然后调用或创建线程或者排队,线程执行完任务后并不会立即被销毁,而是仍然在线程池中等待下一个任务,如果超过存活时间还没有新的任务就会被销毁,通过这样复用线程从而降低开销。
1.2、使用线程池的优点
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- 提供更多更强大的功能。线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
1.3、线程池的核心工作流程
1.3、线程池的五种状态生命周期
1.3.1、RUNNING
状态说明 :线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建(比如调用Executors.newFixedThreadPool()或者使用ThreadPoolExecutor进行创建),就处于RUNNING状态,并且线程池中的任务数为0!
1.3.2、SHUTDOWN
状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
1.3.3、STOP
状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) ->STOP。
1.3.4、TIDYING
状态说明:当所有的任务已终止,记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池 变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING,当线程池在STOP状态下,线程池中执行的任务为空时,会由STOP -> TIDYING。
1.3.5、TERMINATED
状态说明:线程池彻底终止,就变成TERMINATED状态。
状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
1.4、创建线程池的方式
1.4.1、通过 ThreadPoolExecutor 创建
1.4.1.1、线程池的核心参数
//核心线程数
public ThreadPoolExecutor(int corePoolSize,
//最大线程数
int maximumPoolSize,
//线程空闲时间
long keepAliveTime,
//线程空闲时间单位
TimeUnit unit,
//阻塞队列
BlockingQueue<Runnable> workQueue,
//线程工厂
ThreadFactory threadFactory,
//拒绝策略
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
1.4.1.2、线程池的参数详解
corePoolSize :核心线程数,一直保持的线程的数量,即使线程空闲也不会释放。除非设置了 allowCoreThreadTimeout 为 true;
设置规则:
-
CPU密集型:(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1
-
IO密集型:(与CPU密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2
maximumPoolSize:最大线程数,队列满时开启新线程直到等于该值;
设置规则:
- 默认为Integer.MAX_VALUE,一般设置为和核心线程数一样
keepAliveTime:线程空闲时间,即线程池没任务的话,线程存活多久就销毁
设置规则:
- 默认为60s,一般设置为默认60s
unit:线程空闲时间单位
设置规则:
- 默认为秒
workQueue:缓存任务的队列。实现 BlockingQueue。LinkedBlockingQueue:用链表实现的队列,可以是有界的,也可以是无界的,但在Executors中默认使用无界的。
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue
PriorityBlockingQueue
SynchronousQueue
设置规则:
-
ArrayBlockingQueue
-
- ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。
-
LinkedBlockingQueue
-
- LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列。
-
DelayQueue
-
- DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
-
PriorityBlockingQueue
-
- PriorityBlockingQueue(优先级队列)是具有优先级的无界阻塞队列;
-
SynchronousQueue
-
- SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。
threadFactory :线程工厂,一般我们用来给线程命名或者做一些定制时使用
设置规则:
- 创建工厂的两种方式:
- Executors.defaultThreadFactory() 、new ThreadFactoryBuilder().setNameFormat(“task-service-pool-%d”).build()
handler :拒绝策略,即当线程池满了,新来的任务怎么处理,ThreadPooLExecutor自带四种
设置规则:
- 默认是AbortPolicy,会抛出异常。
- 当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
-
- ThreadPoolExecutor.AbortPolicy() 丢弃任务,抛运行时异常。
-
- ThreadPoolExecutor.CallerRunsPolicy 交给主线程去执行,即提交线程任务的线程。
-
- ThreadPoolExecutor.DiscardPolicy 丢弃(不做任何处理)。
-
- ThreadPoolExecutor.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。
1.4.2、通过 Executors 工厂方法创建(生产环境不推荐使用)
序号 | 类型 | 说明 |
---|---|---|
1 | newFixedThreadPool | 创建⼀个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待 |
2 | newCachedThreadPool | 创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程 |
3 | newSingleThreadExecutor | 创建单个线程数的线程池,它可以保证先进先出的执⾏顺序 |
4 | newScheduledThreadPool | 创建⼀个可以执行延迟任务的线程池 |
5 | newSingleThreadScheduledExecutor | 创建⼀个单线程的可以执行延迟任务的线程池 |
6 | newWorkStealingPool | 创建⼀个抢占式执行的线程池(任务执⾏顺序不确定)【JDK1.8 添加】 |
1.4.2.1、newFixedThreadPool (固定大小的线程池)
- fixedThreadPool.execute
public static void main1(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
try {
for (int i = 0; i < 5; i++) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(" fixedThreadPool.execute---" + Thread.currentThread().getName());
}
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//线程池用完,程序结束,关闭线程池(为确保关闭,将关闭方法放入到finally中)
fixedThreadPool.shutdown();
}
}
//运行结果
fixedThreadPool.execute线程池---pool-1-thread-2
fixedThreadPool.execute线程池---pool-1-thread-1
fixedThreadPool.execute线程池---pool-1-thread-3
fixedThreadPool.execute线程池---pool-1-thread-3
fixedThreadPool.execute线程池---pool-1-thread-1
- fixedThreadPool.submit
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
try {
for (int i = 0; i < 5; i++) {
Future<Integer> future = fixedThreadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(9);
System.out.println("随机数-----" + num +"---fixedThreadPool.submit---"+Thread.currentThread().getName());
return num;
}
});
System.out.println("结果---" +future.get());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//线程池用完,程序结束,关闭线程池(为确保关闭,将关闭方法放入到finally中)
fixedThreadPool.shutdown();
}
}
//运行结果
随机数-----5---fixedThreadPool.submit---pool-1-thread-1
结果---5
随机数-----1---fixedThreadPool.submit---pool-1-thread-2
结果---1
随机数-----5---fixedThreadPool.submit---pool-1-thread-3
结果---5
随机数-----4---fixedThreadPool.submit---pool-1-thread-1
结果---4
随机数-----5---fixedThreadPool.submit---pool-1-thread-2
结果---5
execute和submit的区别
1.execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务
2.submit既能提交Runnable类型任务也能提交Callable类型任务。
3.execute()没有返回值
4.submit有返回值,所以需要返回值的时候必须使用submit
特点&注意点
- 核心线程数和最大线程数大小一样
- 没有所谓的非空闲时间,即keepAliveTime为0
- 阻塞队列为无界队列LinkedBlockingQueue
1.4.2.2、newCachedThreadPool (可缓存的线程池)
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
System.out.println(" newCachedThreadPool.execute---" + Thread.currentThread().getName());
});
}
}
//运行结果
newCachedThreadPool.execute---pool-1-thread-1
newCachedThreadPool.execute---pool-1-thread-5
newCachedThreadPool.execute---pool-1-thread-4
newCachedThreadPool.execute---pool-1-thread-2
newCachedThreadPool.execute---pool-1-thread-3
特点&注意点
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
- 核心线程数为0
- 最大线程数为Integer.MAX_VALUE
- 阻塞队列是SynchronousQueue
- 非核心线程空闲存活时间为60秒
当提交任务的速度大于处理任务的速度时,每次提交一个任务,就必然会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。
1.4.2.3、newSingleThreadExecutor(单个线程数的线程池)
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executorService.execute(()-> {
System.out.println(" newCachedThreadPool.execute---" + Thread.currentThread().getName());
});
}
}
//运行结果
newCachedThreadPool.execute---pool-1-thread-1
newCachedThreadPool.execute---pool-1-thread-1
newCachedThreadPool.execute---pool-1-thread-1
newCachedThreadPool.execute---pool-1-thread-1
newCachedThreadPool.execute---pool-1-thread-1
特点&注意点
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(
1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
- 核心线程数为1
- 最大线程数也为1
- 阻塞队列是LinkedBlockingQueue
- keepAliveTime为0
1.4.2.4、newScheduledThreadPool(延迟任务的线程池)
public static void main(String[] args) {
/**
* 创建一个给定初始延迟的间隔性的任务,之后的下次执行时间是上一次任务从执行到结束所需要的时间+给定的间隔时间
*/
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
scheduledThreadPool.scheduleWithFixedDelay(()->{
System.out.println("current Time" + System.currentTimeMillis());
System.out.println(" scheduledThreadPool.scheduleWithFixedDe lay---" + Thread.currentThread().getName());
}, 1, 3, TimeUnit.SECONDS);
}
//运行结果
current Time1693668943517
scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1
current Time1693668946523
scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1
current Time1693668949534
scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1
current Time1693668952536
scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1
特点&注意点
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize,
Integer.MAX_VALUE,
0,
NANOSECONDS,
new DelayedWorkQueue(),
threadFactory);
}
- 最大线程数为Integer.MAX_VALUE
- 阻塞队列是DelayedWorkQueue
- keepAliveTime为0
- scheduleAtFixedRate() :按某种速率周期执行
- scheduleWithFixedDelay():在某个延迟后执行
2、总结
2.1、FixedThreadPool和 SingleThreadPool :
- 允许的请求队列长度为Integer.MAX VALUE,可能会堆积大量的请求,从而导致OOM异常
2.2、CachedThreadPool :
- 允许的创建线程数量为Integer.MAX VALUE,可能会创建大量的线程,从而导致OOM异常