线程池使用及优势
- 线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,
如果线程数量超过了最大数量 超出数量的 线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。 - 它的主要特点为:线程复用,控制最大并发数,管理线程
优点:
1:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
2:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进
行统一的分配,调优和监控
线程池核心类(和参数)
在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池核心类,首先看看线程池类的主要参数有哪些。


- corePoolSize:线程池的核心大小,也可以理解为最小的线程池大小。
- maxPoolSize:最大线程池大小。
- keepAliveTime:空余线程存活时间,指的是超过corePoolSize的空余线程达到多长时间才进行销毁。
- unit:销毁时间单位。
- workQueue:存储等待执行线程的工作队列。
- threadFactory:创建线程的工厂,一般用默认即可(阿里规约不建议使用默认,理由是:创建线程或线程池时请指定有意义的线程名称,方便出错时回溯)。
- handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。
线程池工作流程
1、如果线程池中的线程小于corePoolSize时就会创建新线程直接执行任务。
2、如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。
3、如果工作队列workQueue也满时,当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略(见下文)。
(常用)线程池分类
Executors是jdk里面提供的创建线程池的工厂类,它默认提供了4种常用的线程池应用,而不必我们去重复构造。
newSingleThreadExecutor
单线程线程池,核心线程数和最大线程数均为1,空闲线程存活0毫秒同样无意思,意味着每次只执行一个线程,多余的先存储到工作队列,一个一个执行,保证了线程的顺序执行。
newFixedThreadPool
固定线程池,核心线程数和最大线程数固定相等,而空闲存活时间为0毫秒,说明此参数也无意义,工作队列为最大为Integer.MAX_VALUE大小的阻塞队列。当执行任务时,如果线程都很忙,就会丢到工作队列等有空闲线程时再执行,队列满就执行默认的拒绝策略。
newCachedThreadPool
带缓冲线程池,从构造看核心线程数为0,最大线程数为Integer最大值大小,超过0个的空闲线程在60秒后销毁,SynchronousQueue这是一个直接提交的队列,意味着每个新任务都会有线程来执行,如果线程池有可用线程则执行任务,没有的话就创建一个来执行,线程池中的线程数不确定,一般建议执行速度较快较小的线程,不然这个最大线程池边界过大容易造成内存溢出。
newScheduledThreadPool
调度线程池,即按一定的周期执行任务,即定时任务,对ThreadPoolExecutor进行了包装而已。
线程池使用的注意事项
Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
拒绝策略
- AbortPolicy
简单粗暴,直接抛出拒绝异常,这也是默认的拒绝策略。 - CallerRunsPolicy
如果线程池未关闭,则会在调用者线程中直接执行新任务,这会导致主线程提交线程性能变慢。 - DiscardPolicy
从方法看没做任务操作,即表示不处理新任务,即丢弃。 - DiscardOldestPolicy
抛弃最老的任务,就是从队列取出最老的任务然后放入新的任务进行执行。
提交线程任务
可以先随便定义一个固定大小的线程池
ExecutorService es = Executors.newFixedThreadPool(3);
//提交一个线程
es.submit(xxRunnble);
es.execute(xxRunnble);
提交方式submit和execute区别
- execute没有返回值,如果不需要知道线程的结果就使用execute方法,性能会好很多。
- submit返回一个Future对象,如果想知道线程结果就使用submit提交,而且它能在主线程中通过Future的get方法捕获线程中的异常。
关闭线程池相关方法
shutdown(); 不再接受新的任务,之前提交的任务等执行结束再关闭线程池。
shutdownNow(); 不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程list列表。
awaitTermination(3L, TimeUnit.SECONDS);监测3秒内线程是否结束isTerminated();表示判断shutdown();是否优雅结束完毕
创建线程池示例
根据阿里规约来,创建线程池需要自定义的线程工厂,那么先就创建线程工厂
package com.example.java.base.concurrency.threadpool.factory;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* UserThreadFactory:用户自定义线程工厂(微服务下回创建多个ThreadFactory,前缀已模块命名,比如用户微服务那么就叫UserThreadFactory)</br>
* 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。</br>
* 说明:Executors返回的线程池对象的弊端如下:</br>
* 1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。</br>
* 2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
*
* @author zhangxiaoxiang
* @date 2021/04/21
*/
@Slf4j
public class UserThreadFactory implements ThreadFactory {
private String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
/**
* @param whatFeatureOfGroup 线程组名称(传入机房编号或者业务模块名)
*/
public UserThreadFactory(String whatFeatureOfGroup) {
namePrefix = "UserThreadFactoryPool-" + whatFeatureOfGroup + "-Worker-";
}
/**
* 构造一个新的Thread 。 实现也可以初始化优先级,名称,守护程序状态, ThreadGroup等
*
* @param r 任务
* @return 创建的线程
*/
@Override
public Thread newThread(Runnable r) {
//线程全名:线程(工厂)池-线程组(机房编号或业务模块)-Worker-线程编号
String name = namePrefix + nextId.getAndIncrement();
//group –线程组.如果为null且存在安全管理器,则该组由SecurityManager.getThreadGroup()确定.如果没有安全管理器或SecurityManager.getThreadGroup()返回null,则该组将设置为当前线程的线程组。
//target –启动此线程时调用run方法的对象。 如果为null ,则调用此线程的run方法。
//name –新线程的名称
//stackSize –新线程所需的堆栈大小,或为零以指示该参数将被忽略
//InheritThreadLocals-如果为true ,则从构造线程继承可继承线程局部变量的初始值,否则不继承任何初始值
Thread thread = new Thread(null, r, name, 0, false);
log.info(thread.getName());
return thread;
}
}
创建几个示例任务,用于模拟任务执行
package com.example.java.base.concurrency.threadpool.task;
import lombok.extern.slf4j.Slf4j;
/**
* DemoTask:示例任务
*
* @author zhangxiaoxiang
* @date 2021/4/21
*/
@Slf4j
public class DemoTask implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
log.info("线程名称:【{}】执行了任务", Thread.currentThread().getName());
}
}
最后创建线程池
package com.example.java.base.concurrency.threadpool;
import com.example.java.base.concurrency.threadpool.factory.UserThreadFactory;
import com.example.java.base.concurrency.threadpool.task.DemoTask;
import com.example.java.base.concurrency.threadpool.task.ExecutorTask;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* A10UserThreadPool:自定义线程池
*
* @author zhangxiaoxiang
* @date 2021/04/21
*/
@Slf4j
public class A10UserThreadPool {
public static void main(String[] args) {
//获取CPU核数
final int nThreads = Runtime.getRuntime().availableProcessors();
//默认拒绝策略是AbortPolicy, 抛出RejectedExecutionException拒绝任务的处理程序
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
6,
6,
10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new UserThreadFactory("outbound"),
//显示创建默认拒绝策略(尽量保证线程池的完整创建,有利于理解)
new ThreadPoolExecutor.AbortPolicy());
//示例:默认核心线程6个,最大核心线程数也是6,队列10,那么同时处理的最大任务数是16,下面的for循环提交20个任务,就或超标,要么采用拒绝策略,要么加大线程或者队内容量
//Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7403c468*****
//多线程下同时处理任务的数量区间[0,队列长度+maximunPoolSize],根据任务的处理,分CPU密集型(计算密集型)和IO密集型(数据库文件网络等操作),所以这里核心线程是和最大核心线程数取值就不一样
//CPU型建议:corePollSize=系统核心数+1
//IO型建议:corePollSize=系统核心数*2
// for (int i = 0; i < 20; i++) {
// poolExecutor.submit(new ExecutorTask());
// }
for (int i = 0; i < 7; i++) {
//7个任务完全可以执行,且有空闲
poolExecutor.submit(new ExecutorTask());
}
try {
//测试keepAliveTime参数
Thread.sleep(12000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在上面完全执行的情况下,线程池还有空闲才可以提交任务
for (int i = 0; i < 20; i++) {
//超标的任务就丢弃
poolExecutor.submit(new DemoTask());
}
}
}
执行结果

本文详细介绍了线程池的工作原理,包括线程复用、控制并发数和管理线程的优势。探讨了Java中线程池核心类ThreadPoolExecutor的参数设置,如corePoolSize、maxPoolSize等,并分析了四种常见线程池类型的特点。此外,还讨论了线程池的拒绝策略和创建线程池的注意事项。
6332

被折叠的 条评论
为什么被折叠?



