文章目录
线程池理解
- 对线程池 "池"的理解
可以理解为工厂,如果工厂生产斧头,小王从工厂借了一把,当小王用完了,还给工厂,之后小李也可以借去用
复用已有资源
控制资源总量
你一个任务过来了,我发现池子里有没事干并且还活着的线程,来,拿去用,我也不用费事给你创建一条线程了,要知道线程的创建和销毁可都是麻烦事
你一个任务过来了,我发现池子的线程都在忙,并且现在池子的线程已经太多了,在不限制下去就要内存溢出了,来,排队去
线程池的作用
1、new Thread的弊端
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("--------");
}
}).start();
a. 每次new Thread新建对象性能差:每次都要经历新建,就绪,运行,阻塞,死亡
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
2、相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
Executors介绍
通过Executors可以获得四种线程池
Executors
ExecutorService service = Executors.newCachedThreadPool();
//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。池中物任务时,超过60秒,会自动关闭线程池。
ExecutorService service = Executors.newSingleThreadExecutor();
// 单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。(测试时都是先进先出的规则)
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
//创建一个定长线程池,支持定时及周期性任务执行。ScheduledExecutorService为ExecutorService的子类
ExecutorService service = Executors.newFixedThreadPool(15);
//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
实际项目中都不推荐者四种方式创建线程池,原因如下:
- 阿里开发手册中对于 Java 线程池的使用规范
- 原因2:https://zhuanlan.zhihu.com/p/112527671
例子
- 线程TestThread
public class TestThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+"===="+ getPriority()+"----数据库操作---"+this.getName());
}
}
- 缓存线程池Executors.newCachedThreadPool();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
Thread td = new TestThread();
service.execute(td);
}
- 单例线程池Executors.newSingleThreadExecutor();
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
Thread td = new TestThread();
service.execute(td);
}
// 打印结果
// 1523331778779====5----数据库操作---Thread-0
// 1523331779780====5----数据库操作---Thread-1
// 1523331780780====5----数据库操作---Thread-2
// 1523331781780====5----数据库操作---Thread-3
// 1523331782780====5----数据库操作---Thread-4
// 1523331783780====5----数据库操作---Thread-5
// 1523331784780====5----数据库操作---Thread-6
// 1523331785780====5----数据库操作---Thread-7
// 1523331786781====5----数据库操作---Thread-8
// 1523331787781====5----数据库操作---Thread-9
- 定时线程池Executors.newScheduledThreadPool(10);
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
for (int i = 0; i < 10; i++) {
Thread td = new TestThread();
service.schedule(td, 5, TimeUnit.SECONDS); // 添加任务5秒之后执行
}
- 定长线程池Executors.newFixedThreadPool(15);
ExecutorService service = Executors.newFixedThreadPool(15);
for (int i = 0; i < 10; i++) {
Thread td = new TestThread();
service.execute(td);
}
项目中的用法
package com.hesvit.common.task.execute;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* <p>
* 线程池创建
* <p>
* @author liuping
* @since 2017年12月26日
*/
public class TaskExecute {
// 创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
private static ExecutorService fixedThreadPool;
// 创建固定容量大小的缓冲池
private static ExecutorService singleThreadExecutor;
// 创建容量为1的缓冲池
private static ExecutorService cachedThreadPool;
public static ExecutorService newFixedThreadPool(int nThreads) {
if (fixedThreadPool == null)
return Executors.newFixedThreadPool(nThreads);
else
return fixedThreadPool;
}
public static ExecutorService newSingleThreadExecutor() {
if (singleThreadExecutor == null)
return Executors.newSingleThreadExecutor();
else
return singleThreadExecutor;
}
public static ExecutorService newCachedThreadPool() {
if (cachedThreadPool == null)
return Executors.newCachedThreadPool();
else
return cachedThreadPool;
}
public static void execute(Runnable task) {
newFixedThreadPool(6).execute(task);
}
}
TaskExecute.execute(new RefreshAppUserThread());// 刷新app用户信息
// 线程
public class RefreshAppUserThread extends Thread {
@Override
public void run() {
RedisUtil.refreshAppUser();
}
}
import redis.clients.jedis.Jedis;
@Component
public class RedisUtil {
/** 过时时间为2小时:2 * 60 *60 */
private static int EXPIRE_2_HOUR = 7200;
// 缓存所有App用户
private static String ALL_APP_USERS = "all_app_users";
public static List<User> refreshAppUser() {
return refreshAppUser(null);
}
/**
* 刷新App用户列表
* liuping
*/
public static List<User> refreshAppUser(Jedis redis) {
if (redis == null)
redis = MyJedisPool.getJedisObject();// 获得jedis实例
List<User> users = new ArrayList<>();
try {
User user = new User();
user.setLimit(-1);
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
UserService service = (UserService) context.getBean("userService");
users = service.selectByIndex(user);
if (null != users) {
JSONArray jsonArray = JSONArray.fromObject(users);
String struser = String.valueOf(jsonArray);
redis.set(ALL_APP_USERS, struser);
redis.expire(ALL_APP_USERS, EXPIRE_2_HOUR); // 设置过期时间
}
} catch (Exception e) {
logger.error("Failed to get wxuser from redis: {}", e.getMessage());
} finally {
MyJedisPool.recycleJedisOjbect(redis);
}
return users;
}
}
ThreadPoolExecutor 介绍
1-ThreadPoolExecutor 参数说明
参数说明形象文章: https://zhuanlan.zhihu.com/p/112527671
- 花瓶解释: 瓶口-maximumPoolSize、瓶颈-workQueue、瓶底-corePoolSize
线程池中各个参数如何合理设置: https://blog.csdn.net/riemann_/article/details/104704197
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程空闲时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略)
{
...
}
- corePoolSize -线程池中保留的线程数,即使它们处于空闲状态
- maximumPoolSize - 池中允许的最大线程数
- keepAliveTime -当线程数大于内核时,这是多余空闲线程在终止前等待新任务的最大时间。
- unit - keepAliveTime参数的时间单位
- workQueue——用于在任务执行前保存任务的队列。此队列将仅保存由execute方法提交的可运行任务。
- threadFactory —— 线程工厂,用来创建线程。
- handler—— 当线程边界和队列容量达到时,执行被阻塞时使用的处理程序
拒绝策略,默认是AbortPolicy,会抛出异常。
当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
AbortPolicy 丢弃任务,抛运行时异常。
CallerRunsPolicy 由当前调用的任务线程执行任务。[常用]
DiscardPolicy 忽视,什么都不会发生。
DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。
- ThreadPoolExecutor和spring封装的ThreadPoolTaskExecutor案例
ThreadPoolExecutor是Java的线程池
ThreadPoolTaskExecutor是spring封装的线程池
2-线程池的执行顺序
线程池按以下行为执行任务
- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满,若线程数小于最大线程数,创建线程。
- 若线程数等于最大线程数,则执行拒绝策略
3 线程池配置
package cc.nrbc.bschain.server.common.configure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池配置
*
* @author Feny
* @date Created in 2021/10/20
*/
@EnableAsync
@Configuration
public class ThreadPoolConfig {
private final Integer corePool = 5; //核心线程数
private final Integer maxPool = 10; //最大线程数
private final Integer keepAlive = 30; //线程空闲时间
private final Integer queueCapacity = 1000;
private final RejectedExecutionHandler executionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
@Bean
@Primary
@Order(99)
@ConditionalOnMissingBean(value = ThreadPoolTaskExecutor.class)
public ThreadPoolTaskExecutor initExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// CPU核数 int i = Runtime.getRuntime().availableProcessors();
// 核心线程数目 i * 2
executor.setCorePoolSize(corePool);
//指定最大线程数
executor.setMaxPoolSize(maxPool);
// 队列中最大的数目 i * 2 * 10
executor.setQueueCapacity(queueCapacity);
executor.setAwaitTerminationSeconds(60);
// 线程空闲后的最大存活时间
executor.setKeepAliveSeconds(keepAlive);
// 线程名称前缀
executor.setThreadNamePrefix("Primary-Async-");
// 当调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
/**
* rejection-policy:当pool已经达到max size的时候,如何处理新任务
* CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行对拒绝task的处理策略
*/
executor.setRejectedExecutionHandler(executionHandler);
//加载
executor.initialize();
return executor;
}
// 两个相同的bean 注入是需要加注解 @Qualifier("secondAsync")
@Bean(name = "secondAsync")
public ThreadPoolTaskExecutor secondAsync() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPool);
executor.setCorePoolSize(corePool);
executor.setAwaitTerminationSeconds(60);
executor.setKeepAliveSeconds(keepAlive);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("Second-Async-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setRejectedExecutionHandler(executionHandler);
return executor;
}
/*@Bean
public ThreadPoolExecutor initThreadPoolExecutor() {
return new ThreadPoolExecutor(corePool,
maxPool,
keepAlive,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity), //任务队列
executionHandler); //拒绝策略
}*/
@Bean
public ThreadPoolExecutor initThreadPoolExecutor() {
//获取cpu核心数
int i = Runtime.getRuntime().availableProcessors();
//核心线程数
int corePoolSize = i * 2;
//最大线程数
int maximumPoolSize = i * 2;
//线程无引用存活时间
long keepAliveTime = 60;
//时间单位
TimeUnit unit = TimeUnit.SECONDS;
//任务队列,接收一个整型的参数,这个整型参数指的是队列的长度,
//ArrayBlockingQueue(int,boolean),boolean类型的参数是作为可重入锁的参数进行初始化,默认false,另外初始化了notEmpty、notFull两个信号量。
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue(i * 2 * 10);
/**
* 1. 同步阻塞队列 (put,take),直接提交。直接提交策略表示线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。
* 这种策略需要线程池具有无限增长的可能性。实现为:SynchronousQueue
* 2. 有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,
* 但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,
* 但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,
* CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
* 3. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。
* 这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,
* 适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
*/
/**
* 线程工厂
* defaultThreadFactory() 返回用于创建新线程的默认线程工厂。
* privilegedThreadFactory() 返回一个用于创建与当前线程具有相同权限的新线程的线程工厂。
*/
// ThreadFactory threadFactory =Executors.defaultThreadFactory();
//创建线程池
// return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); // 默认线程工厂
}
}
4 线程池使用
@Api(tags = "买车")
@Slf4j
@Validated
@RestController
@RequestMapping("buyCar")
@RequiredArgsConstructor
public class BuyCarController {
private final ThreadPoolExecutor threadPool;
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(16,
32,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), //任务队列
new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略
public static void main(String[] args) throws Exception {
submitTest();
}
/**
*
* 1 注释掉 future.get() 打印结果:
* 主线程执行完成
* 进入 getName()
* getName()执行完成
* 2 执行 future.get() 打印结果:
* 进入 getName()
* getName()执行完成
* 主线程执行完成
* 由此可见,submit.get(),进行了线程阻塞,获取到结果之后才往下执行
*/
public static void submitTest() throws Exception {
String name = "恰恰";
Future<String> future = pool.submit(() -> getName(name));
/*
* 跟future 一样
Future<String> future2 =pool.submit(new Callable<String>() {
@Override
public String call() {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
}
});*/
future.get();
System.out.println("主线程执行完成");
}
public static String getName(String name) {
System.out.println("进入 getName()");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
System.out.println("getName()执行完成");
return "我是" + name;
}
/**
* 获取用车记录信息
* threadPool.submit() 异步获取返回数据
* 通过get() 获取异步返回数据
*/
@GetMapping("getUseCarRecord")
public NrbcResponse getUseCarRecord(@NotNull(message = "车辆id不能为空") @RequestParam Long carInfoId) throws Exception {
UseCarRecordResponse response = new UseCarRecordResponse();
// 异步获取数据
Future<List<CarClaimRecord>> claimRecord = threadPool.submit(() -> this.getClaimRecord(carInfoId));
Future<List<MileageResponse>> mileageRecord = threadPool.submit(() -> this.getMileageRecord(carInfoId));
response.setClaimRecord(claimRecord.get()); // claimRecord.get(),get()之前进行异步阻塞,有数据返回才往下执行
response.setMileageRecord(mileageRecord.get());
return new NrbcResponse().data(response);
}
/**
* 获取理赔数据
*
* @param carInfoId 车辆信息id
* @author Feny
* @date 2021/10/19
*/
private List<CarClaimRecord> getClaimRecord(Long carInfoId) {
List<CarClaimRecord> list = new ArrayList<>();
// 业务处理......
// list = 数据库.获取列表
return list;
}
private List<MileageResponse> getMileageRecord(Long carInfoId) {
List<MileageResponse> list = new ArrayList<>();
// 业务处理......
// list = 数据库.获取列表
return list;
}
}
5 ThreadPoolExecutor有两种提交方法execute和submit:
-
无返回值的任务使用public void execute(Runnable command) 方法提交;
-
有返回值的任务使用public Future submit(Callable) 方法提交。
Future.get()之前进行异步阻塞,数据返回后才往下执行
6 CountDownLatch 线程计数器
-
一、介绍
CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。它可以使一个或多个线程等待一组事件的发生,而其他的线程则可以触发这组事件。 -
二、特性
- CountDownLatch 可以用于控制一个或多个线程等待多个任务完成后再执行。
- CountDownLatch 的计数器只能够被减少,不能够被增加。
- CountDownLatch 的计数器初始值为正整数,每次调用 countDown() 方法会将计数器减 1,计数器为 0 时,等待线程开始执行。
- demo
private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(16,
32,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), //任务队列
new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略
public static void main(String[] args) throws Exception {
// submitTest();
submitTest2();
}
public static void submitTest2() throws Exception {
// orderList 即数据订单列表
List<Integer> orderList = new ArrayList<>();
for (int i=0;i<10;i++) {
orderList.add(i);
}
List<Future<String>> futureList = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(orderList.size());
for (int i=0;i<10;i++) {
String name = "order_"+i;
Future<String> future = pool.submit(() -> orderHander(name, latch));
// Future<String> future = pool.submit(() -> orderHander(name));
futureList.add(future);
}
latch.await();
for (Future<String> temp : futureList) {
String s = temp.get();
System.out.println("打印Future>:" + s);
}
System.out.println("主线程执行完成");
}
// public static String orderHander(String order) {
public static String orderHander(String order, CountDownLatch latch) {
System.out.println("进入 orderHander()"+order);
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
}
if (Objects.equals("order_3", order))
throw new RuntimeException("订单异常");
// finally {
// 一定要在finally执行,如果不在finally执行,抛了异常,计数器不归0,主线程一直阻塞
latch.countDown();
// }
System.out.println("orderHander()执行完成" + order);
return "订单" + order;
}