一次并发编程问题复盘

本文详细记录了一次在Java并发编程中遇到的问题,从线程池的使用、提交任务的方式到反射读取Jar信息的异常,再到IDEA与命令行启动程序的差异。通过排查,发现问题出在`Reflections`库的文件关闭机制,以及IDEA和`java -jar`启动方式的不同。解决方案是预先加载`Reflections`以避免线程异常。
摘要由CSDN通过智能技术生成

在这里插入图片描述

一次并发编程问题复盘

别人都是代码在测试环境运行正常,然后发布生产环境后发现异常。我这好家伙,在idea中启动一切正常,一旦在命令行启动用Java命令启动,直接就不一样了

问题描述

最近在开发一个项目,其中有一个场景就是启动多个线程来执行多个(2000左右)任务,可在开发的过程中我发现一个奇怪的现象。在我用idea测试我的程序时,无论是启1个线程、10个线程、还是50个线程……和1个任务、100个任务、还是2000个任务,程序运行一切正常,运行结果也正是我所期待的。由于这个项目需要提供一个命令行工具的版本,所以我正一副准备搞完下班的样子将程序打包,然后用Java命令启动测试一下开发已久的成果。结果,果然令我大吃一惊!启动是正常了,可运行速度却只有在idea启动的一层!这是为何,简直让我百思不得其姐啊~~~

通用jstack查线程状态后,有以下发现:

  • idea工具启动程序时,core线程全部启动,且运行状态都是RUNNING状态
  • 通过命令行Java命令启动时,core线程全部启动,可运行状态都是WAITING(parking)状态
  • 程序并无死锁和较大耗时操作

一开始我以为是线程池使用有问题,所以就从线程池入手查找问题原因。这一次让我对ThreadPollExecutor也有了更深的理解,首先让我们来回顾一下它的基本知识点

ThreadPoolExecutor介绍

熟练掌握和使用ThreadPoolExecutor类是每个Java开发人员必备的技能,他能够帮助开发人员合理的管理和和执行并行任务,使用线程池不仅免去了创建和销毁线程等繁琐的线程管理工作,更重要的是极大的提升了程序的响应速度,并且在基于ThreadPoolExecutor可以扩展出更多的功能,例如ScheduledThreadPoolExecutor等

  • corePoolSize & maximumPoolSize: 核心线程数和最大线程数,线程池会根据这两个参数动态调整线程数量。
    若提交任务时线程池中的线程数小于corePoolSize则直接新建线程运行任务
    若提交任务时线程池中的线程数大于corePoolSize且小于maximumPoolSize,任务队列满时会创建新的线程运行任务,否则存入任务队列
  • keepAliveTime & unit: 线程存活时间和时间单位,若当前线程池线程数量大于corePoolSize,空闲的线程或超过keepAliveTime的线程会被终止
  • workQueue: 任务队列,若提交任务时线程池线程数量大于maximumPoolSize,则任务会被放入任务队列中等待执行。当线程池线程数量和workQueue都达到最大值时,新提交的任务将会被拒绝
  • handler: 拒绝策略(RejectedExecutionHandler),当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池

查找问题原因

在解决问题的过程中,我发现了好几个坑,下面就来一一介绍

1. 线程池的提交方法

向ThreadPoolExecutor中提交任务有两种方法,分别是execute和submit。我一开始没有研究他们到底有什么区别,默认都是用submit提交,因为“submit”这个方法名给我的感觉就是提交到任务队列,至于什么时候执行交给线程池去判断。
然鹅,现实给了我一个响亮的耳光,当我苦于思索最开始遇到的问题是什么原因时,其中**“Idea和Java命令行启动都是一切正常”** 这个现象就是在这里掉坑里了,其实他们的现象并不一样,因为submit提交的任务抛出的异常会被线程池catch掉,调用FutureTask的get方法时,才会抛出异常!!!
当用submit提交任务时,任务首先会被封装成一个FutureTask 再调用execute方法执行

    public Future<?> submit(Runnable task) {
   
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

我们都知道,提交新的任务(Runnable)在执行时,肯定会调用run方法来执行具体逻辑,那么这个FutureTask的run方法到底干了什么呢

 public void run() {
   
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
   
            Callable<V> c = callable;
            if (c != null && state == NEW) {
   
                V result;
                boolean ran;</
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值