真实代码肯定不能发出来。模拟代码如下:复制/根据业务修改即用
1.es.submit 有返回值
List<UserRemindRecord> userList = new ArrayList<>();
List<UserRemindRecord> list = new CopyOnWriteArrayList<>();
//原先用的ArrayList 发现获取的数据有问题 并发操作ArrayList不安全啊。!因为你list.add 操作不安全
List<Long> setList = new CopyOnWriteArrayList<>();
ResponseResult<WeixinMessageSubscribeSendOneRsp> result = new ResponseResult<>();
@BeforeEach
public void before() {
long dataCount = 1000;
// 创建订单数据。模拟已经插入到数据库的订单
for (long i = 0; i < dataCount; i++) {
UserRemindRecord orderVO = new UserRemindRecord();
orderVO.setId(i + 1);
orderVO.setUserId(i + 1);
orderVO.setWeiXinUnionid(String.valueOf(i + 1));
orderVO.setWxTemplateId((i +1)+ "temId");
//防止电脑太快,导致都是同一个时间,所以加一个数
orderVO.setCreateTime(Date.from(LocalDateTime.now().plusSeconds(i).atZone(ZoneId.systemDefault()).toInstant()));
userList.add(orderVO);
}
WeixinMessageSubscribeSendOneRsp oneRsp = new WeixinMessageSubscribeSendOneRsp();
oneRsp.setErrcode(200);
result.setData(oneRsp);
log.info("初始化数据================{}", userList.size());
}
@Test
public void mid() throws InterruptedException, ExecutionException {
// WeixinMessageSubscribeSendOneReq req = new WeixinMessageSubscribeSendOneReq();
//A.CountDownLatch latch = new CountDownLatch(userList.size());
//A.这样写是为了下列es.execute(() -> { 无返回值,但是仍想要线程结束,再在主线程中执行代码
//注意位置不同,造成数据混乱 Map<String, TemplateData> m = new HashMap<>();
long startTime = System.currentTimeMillis();
log.info("开始时间start={}", startTime);
// 获取当前系统可用的处理器核心数
int i = Runtime.getRuntime().availableProcessors();
log.info("cpu核心数:{}", i);
//newFixedThreadpool之前用这个,因阿里禁用容易OOM
ExecutorService es = new ThreadPoolExecutor(i * 2, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), new ThreadPoolExecutor.AbortPolicy());
List<List<UserRemindRecord>> partList = Lists.partition(userList, 400);
List<Future<UserRemindRecord>> futures = new ArrayList<>();
for (UserRemindRecord item : userList) {
Future<UserRemindRecord> fut = es.submit(() -> {
Map<String, TemplateData> m = new HashMap<>();
WeixinMessageSubscribeSendOneReq req = new WeixinMessageSubscribeSendOneReq();
req.setTemplate_id(item.getWxTemplateId());
req.setPage("pages/index/index");//跳转到首页
req.setTouser(item.getWeiXinUnionid());
req.setData(m);
req.setLang("zh_CN");
log.info("微信端推送消息数据==={}", req);
// ResponseResult<WeixinMessageSubscribeSendOneRsp> result = feignClient.messageSubscribeSend(req);
WeixinMessageSubscribeSendOneRsp oneRsp = result.getData();
//log.info("推送订阅消息-微信端返回为=={}", oneRsp);
//默认发送成功,记录状态
if (oneRsp.getErrcode() != 0) {
item.setRemindJson(JSONUtil.toJsonStr(oneRsp));
list.add(item);
setList.add(item.getUserId());
}
// A. latch.countDown();
return item;
});
futures.add(fut);
}
// A. latch.await();
for (Future<UserRemindRecord> future : futures) {
//此处作用!future.get();类似 A. latch.countDown(); 只不过让有返回值的线程执行完毕!!!
// 否则 下列list.size=0.因为多线程,线程执行顺序无法预测,不等子线程执行,主线程就执行了。
此时list中都没放进去值啊!
UserRemindRecord one = future.get();
}
// es.shutdown(); 不能关,除非线程池在方法内生产。但是每次新的创建,也耗损资源
long endTime = System.currentTimeMillis();
log.info("结束时间end={}", endTime);
log.info("消耗时间cast={}", endTime - startTime);
System.out.println(list.size());
System.out.println(setList.size());
结果:
1.用es.submit,但是无futures.add(fut);
2.用es.submit,也有futures.add(fut),但是无future.get(); 可理解没循环进行执行get方法
3.用es.excute,但是无 CountDownLatch latch = new CountDownLatch(userList.size());
都导致list.size() setList.size()不正确!
原因: 理解原子性、可见性、有序性 。此处是原子性没保证!
1.多线程环境下并发访问和修改 ArrayList 可能会导致数据不一致或出现异常
2.要等待子线程结束,此时操作的资源副本才能一一归位到自己的集合中[准确性]
2.es.excute 无返回值 但是实现返回值效果
List<UserRemindRecord> userList = new ArrayList<>();
List<UserRemindRecord> list = new CopyOnWriteArrayList<>();
List<Long> setList = new CopyOnWriteArrayList<>();
ResponseResult<WeixinMessageSubscribeSendOneRsp> result = new ResponseResult<>();
@BeforeEach
public void before() {
long dataCount = 1000;
// 创建订单数据。模拟已经插入到数据库的订单
for (long i = 0; i < dataCount; i++) {
UserRemindRecord orderVO = new UserRemindRecord();
orderVO.setId(i + 1);
orderVO.setUserId(i + 1);
orderVO.setWeiXinUnionid(String.valueOf(i + 1));
orderVO.setWxTemplateId((i + 1) + "temId");
//防止电脑太快,导致都是同一个时间,所以加一个数
orderVO.setCreateTime(Date.from(LocalDateTime.now().plusSeconds(i).atZone(ZoneId.systemDefault()).toInstant()));
userList.add(orderVO);
}
WeixinMessageSubscribeSendOneRsp oneRsp = new WeixinMessageSubscribeSendOneRsp();
oneRsp.setErrcode(200);
result.setData(oneRsp);
log.info("初始化数据================{}", userList.size());
}
@Test
public void mid() throws InterruptedException, ExecutionException {
// WeixinMessageSubscribeSendOneReq req = new WeixinMessageSubscribeSendOneReq();
CountDownLatch latch = new CountDownLatch(userList.size());
/**此处有数据问题,两个参数,各自值相同等
Map<String, TemplateData> m = new HashMap<>();*/
long startTime = System.currentTimeMillis();
log.info("开始时间start={}", startTime);
int i = Runtime.getRuntime().availableProcessors();
log.info("cpu核心数:{}", i);
ExecutorService es = new ThreadPoolExecutor(i * 2, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), new ThreadPoolExecutor.AbortPolicy());
List<List<UserRemindRecord>> partList = Lists.partition(userList, 400);
List<Future<UserRemindRecord>> futures = new ArrayList<>();
es.submit(() -> {
for (UserRemindRecord item : userList) {
Map<String, TemplateData> m = new HashMap<>();
WeixinMessageSubscribeSendOneReq req = new WeixinMessageSubscribeSendOneReq();
req.setTemplate_id(item.getWxTemplateId());
req.setPage("pages/index/index");//跳转到首页
req.setTouser(item.getWeiXinUnionid());
req.setData(m);
req.setLang("zh_CN");
log.info("微信端推送消息数据==={}", req);
WeixinMessageSubscribeSendOneRsp oneRsp = result.getData();
try {
Thread.sleep(100);//模拟微信端请求消耗时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//log.info("推送订阅消息-微信端返回为=={}", oneRsp);
//默认发送成功,记录状态
if (oneRsp.getErrcode() != 0) {
item.setRemindJson(JSONUtil.toJsonStr(oneRsp));
list.add(item);
setList.add(item.getUserId());
}
latch.countDown();
}
});
latch.await();
long endTime = System.currentTimeMillis();
log.info("结束时间end={}", endTime);
log.info("消耗时间cast={}", endTime - startTime);
System.out.println(list.size());//看是否丢失数据
System.out.println(setList.size());//判断返回的数据是否混乱
}
此处有个改动。1.线程执行与集合内外互换了下2.加了个Thread.sleep(100)模拟微信端处理请求时间
看结果时间:106960 ~107秒
线程池只有一个线程再工作—》进行对集合的操作
改为原来集合外层,es.escute内层时 时间:9198 ~9秒 图中touser也不是顺序执行
我的电脑内核:
9*6*2=108 ~107秒 啧啧!反证:一个多线程操作,另一个虽然多线程,但是只是一个线程在工作。没利用多线程优势!
不知道线程池在集合外还是集合里:概念混淆!或者说 明白 线程池里干嘛的 执行资源!!!
在集合内,线程池执行集合中某个资源!线程池空闲线程去争取该资源
在集合外,线程池执行集合整体资源!争取该整体==跟同步想想没区别
3.关于线程池的拒绝策略问题
private ExecutorService es = new ThreadPoolExecutor(i * 2, 2000, 15, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadPoolExecutor. CallerRunsPolicy());
重点想说策略问题:
若线程池中的核心线程数被用完且阻塞队列已排满,则此时线程池的资源已耗尽,线程池将没有足够的线程资源执行新的任务。为了保证操作系统的安全,线程池将通过拒绝策略处理新添加的线程任务
实际开发中:大致举例:线程最大树数200,队列1000,但是你要发送的实际人数1万人,此时不同策略就会有不同效果!
AbortPolicy(默认) | 超过了资源如1200,抛异常,报错! | 抛出 RejectedExecutionException 异常 ,表示拒绝执行新任务 |
DiscardPolicy | 超过了资源如1200,抛多余任务 | 不执行,但不报错,会丢失后续数据 |
DiscardOldestPolicy | 超过了资源如1200,抛弃旧任务 | 队列中前面1200个内时间久未处理的任务丢弃,尝试提交新当前任务,会丢失数据 |
CallerRunsPolicy | 超过了资源如1200 | 新任务交给主线程执行,但是降低整体的吞吐量。 |
拒绝新提交任务: 1.当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部依然有没执行完的任务正在执行,但是由于线程池已经关闭,此时如果再向线程池内提交任务,就会遭到拒绝。 |
总结:线程池中的任务队列已满,并且总线程数已达到最大线程数,则根据拒绝策略来决定如何处理该任务
线程池的五种拒绝策略_Fighting0531的博客-CSDN博客 参考图例好理解
4.多线程中ConcurrentModificationException 等异常原因
参考下列文章讲的确实好啊!
java.util.ConcurrentModificationException 异常问题详解 - snowater - 博客园 (cnblogs.com)
多线程学的很惭愧。希望有所帮助
参考:java多线程:使用newFixedThreadPool方法创建指定线程数量的线程池_线程池创建固定线程数_zhaoliubao1的博客-CSDN博客
多线程快速处理List集合(结合线程池的使用)_线程池处理list_凌晨里的无聊人的博客-CSDN博客
5.线程池初始化过程 + 使用 new ThreadPoolExecutor
线程池作用:
1.线程的创建和销毁都会消耗一定的性能,通过线程池可以减少线程 的创建和销毁带来的性能消耗 【概括为提提升性能,减少性能消耗】
2.方便对线程统一的维护管理(定时开启,周期执行,并发数控制等) 【概括为 方便管理】
相关参数含义:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)构造方法重载,多个形式参数列表
- corePoolSize 核心线程数,队列没满时,线程最大的并发数
- maximumPoolSize 线程池最大线程数,队列满时,线程最大并发数
- keepAliveTime 空闲线程的最大存活时间,系统默认只回收非核心线程,核心线程可以通过 设置 threadPoolExecutor.allowCoreThreadTimeOut(true);来运行核心 线程的销毁。
- TimeUnit 空闲时间的时间单位
- BlockingQueue workQueue 线程池中的阻塞队列类型(下面详细介绍各种阻塞队列的区别)
- ThreadFactory threadFactory 线程工厂,用于创建线程 比如给线程改名字
- RejectedExecutionHandler handler 拒绝策略,当线程池数量达到最大线程数限制时,采取的具体执行措施
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name = " + Thread.currentThread().getName());
}
};
//核心线程数为5,最大线程数为10
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,
5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));threadPoolExecutor.execute(runnable)//任务1
threadPoolExecutor.execute(runnable);//任务2
threadPoolExecutor.execute(runnable);//任务3
System.out.println("核心线程数 = " + threadPoolExecutor.getCorePoolSize());
System.out.println("当前线程池线程数 = "+threadPoolExecutor.getPoolSize());
System.out.println("最大线程数= " + threadPoolExecutor.getMaximumPoolSize());
System.out.println("当前队列中的任务数 queue size = " + threadPoolExecutor.getQueue().size());
}
以上述代码为例!只改变任务数【 threadPoolExecutor.execute(runnable)】,其余代码不变动
开启任务数 | 核心线程数 | 当前线程池线程数 | 最大线程数 | 队列任务数 | 备注 |
3 | 5 | 3 | 10 | 0 | 正常 |
8 | 5 | 5 | 10 | 3 | 正常 |
18 | 5 | 8 | 10 | 10 | 正常 |
25 | 5 | 10 | 10 | 10 | 异常 |
异常原因:当开启任务超过20个时,会通过拒绝策略来执行,默认拒绝执行抛出异常。
当workQueue/队列已满,且提交任务数超过maximumPoolSize/线程池最大线程数,任务由RejectedExecutionHandler处理
最大任务数:最大线程数+队列任务数
总结/优点: 根据业务自己设定参数来自定义线程池:
//使用了ArrayBlockingQueue 有界队列,核心线程数为5,最大线程数为10,空闲时间为5s
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,
5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
6.建议使用ThreadPoolExecutor反例
- 固定线程池:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
缺点:默认无界队列(可以通过参数设定有限)最大值为Integer.MAX_VALUE;
核心线程数 = 最大线程数;任务队列是无界队列,当并发数很高时,慎用,容易内存溢出- 可缓存的线程池:
public static ExecutorService newCachedThreadPool() {
缺点:队列为SynchronousQueue 任务队列中只能存放一个元素,放入一个元素必须消费掉 才可以再放入第二个,否则一直阻塞;一个可以无限扩大的线程池。 每次新来呀一个任务,总是会新建一个线程来执行,当达到最大线程数时, 可以通过空闲(60s)线程来执行。;用于处理任务时间短,处理速度大于提交速度的场景。对CPU要求高
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}- 单一线程池 :
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
缺点:无界队列 ,最大值为Integer.MaxValue;核心线程数 = 最大线程数 = 1;空闲时间为 0 ,任务队列无界注意OOM- 定时任务线程池:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
解释:队列为DelayedWorkQueue ,有界队列;最大线程数无界[Integer.MAX_VALUE];可以设置定时执行,延时执行等- 工作窃取线程池:
public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); } 解释:利用了ForkJoinPool进行实现;java1.8提供,适用于比较耗时的并发操作;工作窃取线程,当一个线程执行完以后,会去执行其他线程的任务;使得任务的负载更加均衡,从而提高执行效率;最大限度地利用 CPU 的计算资源 注意:工作窃取线程池的特殊性质,它不适用于所有类型的任务。对于 I/O 密集型任务,使用 newWorkStealingPool 并不能提高执行效率,反而可能会降低性能// 创建一个工作窃取线程池,大小为系统CPU核心数 ExecutorService executor = Executors.newWorkStealingPool(); // 提交一组任务 for (int i = 0; i < 10; i++) { executor.submit(() -> { // 执行任务代码 }); } // 关闭线程池 executor.shutdown();
总结:建议使用自定义的 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);参数可以自己根据业务控制!
6.1举例:
ThreadPoolConfigProperties属性类
@ConfigurationProperties(prefix = "xx.thread")
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
ThreadPoolConfig配置类
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
ThreadFactory espThreadFactory = new ThreadFactoryBuilder().setNameFormat("xx-pool-%d").build();
return new ThreadPoolExecutor(
pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000),
espThreadFactory,
new ThreadPoolExecutor.AbortPolicy()
);
}
}
异步调用
1. @Async("threadPoolExecutor")
@Override
public void testCallThread(MarketingReq marketingReq) {
//里面自己的业务逻辑代码
}
2.类中注入
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
方法中使用
public ResponseResult (Long id, Integer param) {
if (id== null || param== null) {
return ResponseResult.fail("当前用户id为空", BaseResponseCodeEnum.PARAM_IS_INVALID);
}
//异步远程调用获取角色权限
//根据用户id获取权限
CompletableFuture<ResponseResult<List<XXDTO>>> roleFuture = CompletableFuture.supplyAsync(() -> service.queryListByRegister(id, param), threadPoolExecutor);
//获取无权限所有人可见的公用角色
CompletableFuture<ResponseResult<List<XXDTO>>> noRoleFuture = CompletableFuture.supplyAsync(() -> service.queryNoRole(param), threadPoolExecutor);
CompletableFuture<List<XXDTO>> listCompletableFuture = roleMenuFuture.thenCombine(noRoleFuture, (roleRes, noRileRes) -> {
List<XXDTO> systemDtoList = new ArrayList<>();
systemDtoList .addAll(roleRes.getData());
systemDtoList .addAll(noRileRes.getData());
return systemDtoList ;
});
List<XXDTO> dtoList = listCompletableFuture.get();
return ResponseResult.ok(dtoList);
}
7.了解CountDownLatch
概念:
一个同步工具类,它通过一个计数器来实现的,初始值为线程的数量。每当一个线程完成了自己的任务,计数器的值就相应得减1。当计数器到达0时,表示所有的线程都已执行完毕,然后在等待的线程就可以恢复执行任务。
方法解释:
构造器:
CountDownLatch(int count):count为计数器的初始值(一般需要多少个线程执行,count就设为几)。
方法:
countDown(): 每调用一次计数器值-1,直到count被减为0,代表所有线程全部执行完毕。
getCount():获取当前计数器的值。
await(): 等待计数器变为0,即等待所有异步线程执行完毕。[调用该方法的线程会阻塞,直到计数器的值减少到0]
boolean await(long timeout, TimeUnit unit): 此方法与await()区别:
①此方法至多会等待指定的时间,超时后会自动唤醒,若 timeout 小于等于零,则不会等待
②boolean 类型返回值:若计数器变为零了,则返回 true;若指定的等待时间过去了,则返回 false
应用场景
可以实现一些需要等待其他线程完成任务的场景,例如主线程等待多个子线程全部执行完毕后再进行某种操作,或者多个子线程等待某个共享资源就绪后再同时开始执行。
代码实例
import java.util.concurrent.CountDownLatch;
public class MainThread {
public static void main(String[] args) throws InterruptedException {
int threadCount = 5;
//设置初始值== 线程数
CountDownLatch latch = new CountDownLatch(threadCount);
// 创建并启动多个子线程
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new WorkerThread(latch));
thread.start();
}
// 主线程等待所有子线程执行完毕
latch.await();
// 所有子线程执行完毕后,主线程继续执行其他操作
System.out.println("All worker threads have finished.");
}
}
class WorkerThread implements Runnable {
private final CountDownLatch latch;
public WorkerThread(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
// 模拟子线程执行一些任务
try {
Thread.sleep(1000);
System.out.println("Worker thread has finished its task.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 子线程完成任务后,计数器减1
latch.countDown();
}
}
注意:初始值/线程数 与 子线程/启动线程数 数量一致!
8.了解CompletableFuture
Java8中CompletableFuture实现异步任务编排以及示例_java并发 completablefuture异步编程的实现-CSDN博客
9.推荐概念学习
Java多线程编程实战指南(核心篇)读书笔记(一)_《java多线程编程实战指南(核心篇)》-CSDN博客深入Java线程池:从设计思想到源码解读_completedabruptly-CSDN博客 ***