目录
前言
线程池,顾名思义,存放线程的池子。线程是比较稀缺的资源,被无限创建的话,会大量消耗系统资源,降低系统的稳定性。JAVA 中的线程池,可以对线程进行统一的管理。
1.线程池的优缺点
线程池解决的核心问题就是资源管理问题。使用线程池的优点:
-
降低资源损耗。通过复用已经创建的线程,来降低线程创建销毁带来的消耗。
-
提高响应速度。当接到任务时,减免了原本需要创建线程所消耗的时间。
-
提高了线程的可管理性。线程由线程池统一管理,调度。合理的使用线程池,能够避免线程的随意创建销毁,增加系统稳定性,控制线程数量,也可以避免资源耗尽的风险。
-
提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行
在并发环境下,系统不能够确定在任意时刻中,执行多少任务,投入多少资源。这种不确定性可能会带来一些问题:
-
频繁的创建,删除线程,会带来一定的性能损耗。
-
线程无限创建,可能带来资源耗尽的风险。
-
线程随意的创建,删除。可能会导致系统的不稳定。
2.JAVA 中线程池的继承关系图
Executor 接口是线程框架最基础的部分,该接口只定义一个用于执行 Runnable 的 execute 方法,用来分离任务的提交和任务的执行。
ExecutorService 接口继承了 Executor,在其基础上做了 submit(), shutdown(), invokeAll() 等方法的扩展,算是真正意义上的线程池接口。
AbstractExecutorService 抽象类实现了 ExecutorService 中的部分方法,如部分的 submit(), invokeAll()方法。
ThreadPoolExecutor 是线程池的核心实现类,用来执行被提交的任务。
ScheduledExecutorService 接口继承了 ExecutorService 接口,提供了一些延时执行的方法。
ScheduledThreadPoolExecutor 是一个实现类,可以在给定一个延时后运行命令,或者定时执行命令。
从继承图可以看出,ExecutorService 是线程池的一个比较核心的接口类。该类里面继承了和定义了一些具体的方法:
-
void execute(Runnable command); 执行 Runnable 类型的任务。
-
Future<?> submit(Runnable/Callable task); 用来提交 Runnable/Callable 任务,并返回该任务的 Future 对象。
-
void shutdown(); 在完成已经提交的任务后,关闭。不再接收新的任务。
-
List shutdownNow(); 停止所有正在执行的任务,同时不再接收新的任务。
-
boolean isTerminated(); 判断是否所有的任务都已经执行完成了。
-
boolean isShutdown(); 判断 ExecutorService 是否被关闭。
-
List<Future> invokeAll(Collection<? extends Callable> tasks); 执行指定的任务集合,执行完成后返回结果。
-
T invokeAny(Collection<? extends Callable> tasks); 执行指定的任务集合,任意一个任务执行完成,返回结果,其他任务终止。
3.核心参数与拒绝策略
核心参数:
corePoolSize:线程池中用来工作的核心的线程数量。
maximumPoolSize:最大线程数,线程池允许创建的最大线程数。
keepAliveTime:超出 corePoolSize 后创建的线程存活时间或者是所有线程最大存活时间,取决于配置。
unit:keepAliveTime 的时间单位。
workQueue:任务队列,是一个阻塞队列,当线程数已达到核心线程数,会将任务存储在阻塞队列中。
threadFactory :线程池内部创建线程所用的工厂。
handler:拒绝策略;当队列已满并且线程数量达到最大线程数量时,会调用该方法处理该任务。
提交优先级:从核心线程数 队列 最大线程数
执行优先级:从核心线程数 最大线程数 队列
拒绝策略:
拒绝策略:就是当所请求的线程数量大于线程池的核心线程数的时候,先把线程任务放到任务队列里边,如果阻塞队列满后,还有线程任务直到线程池的最大线程数后,才会开启拒绝策略。
第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满(默认的拒绝策略)
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
4.线程池分类和适用场景
newCachedThreadPool(可缓存)
- 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
- 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
- 适用:执行很多短期异步的小程序或者负载较轻的服务器(可能造成cpu100%)
newFixedThreadPool:(固定大小)
- 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无界阻塞队列
- 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
- 适用:执行长期的任务,性能好很多
newSingleThreadExecutor:(单个)
- 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
- 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
- 适用:一个任务一个任务执行的场景
NewScheduledThreadPool:(定时)
- 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
- 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
- 适用:周期性执行任务的场景
5.工作中的使用demo-1
在阿里巴巴手册中有一条建议:
【强制】线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
如果经常基于Executors提供的工厂方法创建线程池,很容易忽略线程池内部的实现。特别是拒绝策略,因使用Executors创建线程池时不会传入这个参数,直接采用默认值,所以常常被忽略。
@Service
public class ThreadExecutorService {
/**
* 等级规则调整等级线程池
*/
ThreadPoolExecutor memberGradeThreadPoolExecutor = new ThreadPoolExecutor(10, 20, 1000, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10000));
public void memberGradeTaskExecutor(Runnable runnable) {
memberGradeThreadPoolExecutor.execute(runnable);
}
}
@Slf4j
public class GradeTask implements Runnable {
// @Resource
// private UserClient userClient;
//
// @Resource
// private MemberMapper memberMapper;
private String storeOrgId;
private String modifiedId;
private Integer modifyTime;
public GradeTask(String storeOrgId, Integer modifyTime, String modifiedId) {
this.storeOrgId = storeOrgId;
this.modifiedId = modifiedId;
this.modifyTime = modifyTime;
}
@Override
public void run() {
/**
* 1.写你在多线程里面要执行的逻辑
*/
System.out.println("线程开启;我就是来试试呀" + modifyTime);
// MemberDo member = new MemberDo();
// BeanUtils.copyProperties(memberDo, member);
// BaseResponse<String> levelIdResponse = gradeClient.queryLevelByOrgId(storeOrgId, memberDo.getGrowthBalance());
// Assert.isTrue(ResultCode.Codes.SUCCESS.getCode().equals(levelIdResponse.getCode()), levelIdResponse.getMessage());
// memberDo.setLevelId(levelIdResponse.getData());
// memberDo.setModifyTime(new Date());
// memberMapper.updateByPrimaryKeySelective(memberDo);
// member.setNewLevelId(memberDo.getLevelId());
}
}
@Autowired
private ThreadExecutorService threadExecutorService;
@Test
void testThreadPool() {
/**
* 等级规则调整等级线程池
*/
GradeTask gradeTask=new GradeTask("11",1,"111");
GradeTask gradeTas2=new GradeTask("22",2,"222");
GradeTask gradeTas3=new GradeTask("33",3,"333");
GradeTask gradeTas4=new GradeTask("4",4,"444");
List<GradeTask>list=new ArrayList<>();
list.add(gradeTask);
list.add(gradeTas2);
list.add(gradeTas4);
list.add(gradeTas3);
for (GradeTask task : list) {
threadExecutorService.memberGradeTaskExecutor(task);
}
}
6.工作中的使用demo-2
6.1定义线程池
package com.onlyqi.daydayupgo01.springSkill01.thread;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public interface IActionManager {
int CORE_THREAD_SIZE = 5;
int MAX_THREAD_SIZE = 10;
AtomicInteger THREAD_NUMBER = new AtomicInteger(1);
String NAME_PREFIX = "AutoTaskPool-actionManager-";
ThreadFactory SUB_THREAD_FACTORY = r -> new Thread(r, NAME_PREFIX + THREAD_NUMBER.getAndIncrement());
ThreadPoolExecutor ACTION_EXECUTOR = new ThreadPoolExecutor(CORE_THREAD_SIZE, MAX_THREAD_SIZE, 30,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(), SUB_THREAD_FACTORY);
/**
* 执行线程
* @param action
*/
void action(Runnable action);
}
线程池接口的实现
package com.onlyqi.daydayupgo01.springSkill01.thread;
import org.springframework.stereotype.Component;
@Component("actionManager")
public class DefaultActionManager implements IActionManager {
/**
* 提交异步任务
* @param action
*/
@Override
public void action(Runnable action){
ACTION_EXECUTOR.execute(action);
}
}
6.2定义线程任务
package com.onlyqi.daydayupgo01.springSkill01;
public class TestSpring01 implements Runnable {
final private AutoTaskCtrl autoTaskCtrl;
public TestSpring01(AutoTaskCtrl autoTaskCtrl) {
this.autoTaskCtrl = autoTaskCtrl;
}
@Override
public void run() {
System.out.println("wohaoshuai=======我好帅============");
autoTaskCtrl.autoTaskStart();
}
}
package com.onlyqi.daydayupgo01.springSkill01;
public class TestSpring02 implements Runnable{
final private AutoTaskCtrl autoTaskCtrl;
public TestSpring02(AutoTaskCtrl autoTaskCtrl) {
this.autoTaskCtrl = autoTaskCtrl;
}
@Override
public void run() {
autoTaskCtrl.autoTaskStop();
}
}
6.3 统一的线程处理逻辑
package com.onlyqi.daydayupgo01.springSkill01;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class AutoTaskCtrl {
private final static String PROCESS_KEYS_NOT = "320998";
/**
* 推送关闭自动派单消息
* 判断当前时间在大于“系统配置【76612】的自动关闭派单时间”的5分钟之内,推送关闭自动派单的mc消息
*/
public void autoTaskStart() {
System.out.println("===========================1111====================");
}
public void autoTaskStop() {
System.out.println("===========================222====================");
}
}
6.4 线程任务的运行
@SpringBootTest
class DaydayupGo01ApplicationTests {
@Autowired
private IActionManager actionManager;
@Autowired
private AutoTaskCtrl autoTaskCtrl;
@Test
void contextLoadsOA() {
actionManager.action(new TestSpring01(this.autoTaskCtrl));
actionManager.action(new TestSpring02(this.autoTaskCtrl));
}
}
运行结果 :
7.线程池如何合理配置线程数?
7.1 CPU密集型
定义:CPU密集型的意思就是该任务需要大量运算,而没有阻塞,CPU一直全速运行。
CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程)。
CPU密集型任务配置尽可能少的线程数。
“CPU密集型线程数配置公式:(CPU核数+1)个线程的线程池
7.2 IO密集型:
定义:IO密集型,即该任务需要大量的IO,即大量的阻塞。
在单线程上运行IO密集型任务会导致浪费大量的CPU运算能力浪费在等待。
所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间。
第一种配置方式:
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。
“配置公式:CPU核数 * 2。
第二种配置方式:
IO密集型时,大部分线程都阻塞,故需要多配置线程数。
“配置公式:CPU核数 / (1 – 阻塞系数)(0.8~0.9之间)
比如:8核 / (1 – 0.9) = 80个线程数
幸福就是:肉体无痛苦,灵魂无纷扰