多线程发送微信订阅消息+部分多线程介绍

 真实代码肯定不能发出来。模拟代码如下:复制/根据业务修改即用

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 等方法关闭线程池后,即便此时可能线程池内部依然有没执行完的任务正在执行,但是由于线程池已经关闭,此时如果再向线程池内提交任务,就会遭到拒绝。
2.线程池没有能力继续处理新提交的任务,也就是工作已经非常饱和的时候。

总结:线程池中的任务队列已满,并且总线程数已达到最大线程数,则根据拒绝策略来决定如何处理该任务 

线程池的五种拒绝策略_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)】,其余代码不变动

开启任务数核心线程数当前线程池线程数最大线程数队列任务数备注
353100正常
855103正常
18581010正常
255101010异常
异常原因:当开启任务超过20个时,会通过拒绝策略来执行,默认拒绝执行抛出异常。
当workQueue/队列已满,且提交任务数超过maximumPoolSize/线程池最大线程数,任务由RejectedExecutionHandler处理

最大任务数:最大线程数+队列任务数 

总结/优点: 根据业务自己设定参数来自定义线程池:

//使用了ArrayBlockingQueue 有界队列,核心线程数为5,最大线程数为10,空闲时间为5s
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,
                5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));

6.建议使用ThreadPoolExecutor反例

  1. 固定线程池:
    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
     }
    缺点默认无界队列(可以通过参数设定有限)最大值为Integer.MAX_VALUE;
    核心线程数 = 最大线程数;任务队列是无界队列,当并发数很高时,慎用,容易内存溢出
  2. 可缓存的线程池:

    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }

    缺点:队列为SynchronousQueue 任务队列中只能存放一个元素,放入一个元素必须消费掉 才可以再放入第二个,否则一直阻塞;一个可以无限扩大的线程池。 每次新来呀一个任务,总是会新建一个线程来执行,当达到最大线程数时, 可以通过空闲(60s)线程来执行。;用于处理任务时间短,处理速度大于提交速度的场景。对CPU要求高
  3. 单一线程池 :
    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    缺点:无界队列 ,最大值为Integer.MaxValue;核心线程数 = 最大线程数 = 1;空闲时间为 0 ,任务队列无界注意OOM
  4. 定时任务线程池:
     public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
                  new DelayedWorkQueue());
        }
    解释:队列为DelayedWorkQueue ,有界队列;最大线程数无界[Integer.MAX_VALUE];可以设置定时执行,延时执行等
  5. 工作窃取线程池
    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博客     ***

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值