当前位置: 首页 > 后端技术 > Java

java开发技术中Executors创建线程池的缺点

时间:2023-04-02 00:38:42 Java

1。通过Executors创建线程池的缺点在创建线程池的时候,大部分人还是会选择使用Executors来创建。下面是一个创建定长线程池(FixedThreadPool)的例子。严格来说,使用如下代码创建线程池时,是不符合编程规范的。ExecutorServicefixedThreadPool=Executors.newFixedThreadPool(5);原因是:(取自阿里编码协议)不允许使用Executors创建线程池,而是通过ThreadPoolExecutor创建。这种处理方式让写作的同学更加清楚线程池的运行规律,避免资源耗尽的风险。说明:Executors各方法的缺点:1)newFixedThreadPool和newSingleThreadExecutor:主要问题是请求处理队列累积起来可能会消耗非常大的内存,甚至OOM。2)newCachedThreadPool和newScheduledThreadPool:主要问题是最大线程数为Integer.MAX_VALUE,可能会创建非常多的线程,甚至OOM。2、通过ThreadPoolExecutor创建线程池那么,针对上述不规范的代码,重构通过ThreadPoolExecutor创建线程池。/***使用给定的初始*参数和默认线程工厂创建一个新的{@codeThreadPoolExecutor}。**@paramcorePoolSize要保留在池中的线??程数,即使*如果它们处于空闲状态,除非设置了{@codeallowCoreThreadTimeOut}*@parammaximumPoolSize*池中允许的最大线程数*@paramkeepAliveTime当线程数大于*核心,这是多余的空闲线程*在终止前等待新任务的最长时间。*@paramunit{@codekeepAliveTime}参数的时间单位*@paramworkQueue在执行任务之前用于保存任务的队列。该队列将仅保存{@codeRunnable}*由{@codeexecute}方法提交的任务。*@paramhandler执行被阻塞时使用的处理程序*因为达到了线程边界和队列容量*@throwsIllegalArgumentException如果一个以下持有:
*{@codecorePoolSize<0}
*{@codekeepAliveTime<0}
*{@codemaximumPoolSize<=0}
*{@codemaximumPoolSizeworkQueue,RejectedExecutionHandlerhandler){this(corePoolmaximumPoolSize,keepAliveTime,unit,workQueue,Executors.defaultThreadFactory(),handler);}ThreadPoolExecutor是线程池的核心。实现java训练线程的创建和终止需要很大的开销。线程池预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,每个线程池都维护一些基本的统计信息,方便线程管理和监控。3、ThreadPoolExecutor参数解释下面是对其参数的解释。在创建线程池的时候,需要根据自己的情况合理设置线程池。corePoolSize&maximumPoolSize核心线程数(corePoolSize)和最大线程数(maximumPoolSize)是线程池中两个非常重要的概念,希望同学们能够掌握。当有新任务提交到池中时,如果当前运行的线程数小于核心线程数(corePoolSize),即使当前有空闲线程,也会创建一个新线程来处理新提交的任务;如果当前运行的线程数大于核心线程数(corePoolSize)且小于最大线程数(maximumPoolSize),只有等待队列满了才会创建新线程。keepAliveTime&unitkeepAliveTime是超过corePoolSize线程数的线程最大空闲时间,unit是时间单位。等待队列任何阻塞队列(BlockingQueue)都可以用来传递或保存提交的任务。线程池的大小和阻塞队列相互约束线程池:如果正在运行的线程数小于corePoolSize,则在提交新任务时会创建一个新线程运行;如果正在运行的线程数大于等于corePoolSize,则新提交的任务会在队列中等待;如果队列已满,且正在运行的线程数小于maximumPoolSize,则创建一个新线程运行;如果线程数大于maximumPoolSize,新提交的任务将按照拒绝策略进行处理。我们来看看三种通用的入队策略:直接传递:通过SynchronousQueue将任务直接传递给线程。如果当前没有线程可用,则入队尝试将失败并创建一个新线程。该策略可以防止请求在处理可能具有内部依赖性的请求时被锁定。传递通常需要无限制的最大线程数(maximumPoolSize)以避免拒绝新提交的任务。当任务继续以比它们可以处理的速度更快的平均速度到达时,这可能会导致线程的无限增长。无界队列:使用无界队列(如LinkedBlockingQueue)作为等待队列。当所有核心线程都在处理任务时,新提交的任务会进入队列等待。因此,不会创建大于corePoolSize的线程(maximumPoolSize也没有效果)。这种策略适用于每个任务完全独立于其他任务的情况;比如网络服务器。这种类型的等待队列可以平滑突发的高频请求。当任务继续以比它们可以处理的速度更快的平均速度到达时,这可能会导致等待队列无限增长。有界队列:有界队列如ArrayBlockingQueue可以在使用有限的最大线程数时防止资源耗尽,但难以调优和控制。队列大小和线程池大小可以相互作用:使用大队列和少量线程可以减少CPU使用率、系统资源和上下文切换开销,但如果任务频繁阻塞(例如通过I/Olimit),系统可以调度更多线程的执行时间。使用小队列通常需要更多的线程,这样可以最大限度地提高CPU使用率,但可能需要更大的调度开销,从而降低吞吐量。拒绝策略当线程池关闭或饱和时(最大线程和队列都已满),新提交的任务将被拒绝。ThreadPoolExecutor定义了四种拒绝策略:AbortPolicy:默认策略,当需要拒绝任务时抛出RejectedExecutionException;CallerRunsPolicy:直接在execute方法的调用线程中运行被拒绝的任务。如果线程池关闭,任务将被丢弃;DiscardPolicy:直接丢弃任务;DiscardOldestPolicy:丢弃队列中等待时间最长的任务,执行当前提交的任务。如果线程池关闭,任务将被丢弃。我们也可以自定义拒绝策略,只需要实现RejectedExecutionHandler;需要注意的是,拒绝策略的运行需要指定线程池和队列的容量。4.ThreadPoolExecutor如何创建线程通过下面的demo来了解ThreadPoolExecutor创建线程的过程。importjava.util.concurrent.ArrayBlockingQueue;importjava.util.concurrent.BlockingQueue;importjava.util.concurrent.ThreadPoolExecutor;importjava.util.concurrent.TimeUnit;/**通过ThreadPoolExecutor测试线程的执行顺序**/publicclassThreadPoolSerialTest{publicstaticvoidmain(String[]args){//核心线程数intcorePoolSize=3;//最大线程数intmaximumPoolSize=6;//超过corePoolSize线程数的线程最大空闲时间longkeepAliveTime=2;//以秒为时间单位TimeUnitunit=TimeUnit.SECONDS;//创建工作队列,存放提交的等待执行的任务BlockingQueueworkQueue=newArrayBlockingQueue(2);ThreadPoolExecutorthreadPoolExecutor=null;try{//创建线程池threadPoolExecutor=newThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,newThreadPoolExecutor.AbortPolicy());//循环提交任务for(inti=0;i<8;i++){//提交任务Indexfinalintindex=(i+1);threadPoolExecutor.submit(()->{//线程打印输出System.out.println("大家好,我是线程:"+index);try{//模拟线程执行时间,10sThread.sleep(10000);}catch(InterruptedExceptione){e.printStackTrace();}});//每次任务提交后,sleep500ms再提交下一个任务,用于保证提交Thread的顺序。睡眠(500);}}catch(InterruptedExceptione){e.printStackTrace();}最后{threadPoolExecutor.shutdown();}}}执行结果:这里描述一下执行过程:首先通过ThreadPoolExecutor构造函数创建一个线程池;执行for循环,提交8个任务(正好等于maximumPoolSize[最大线程数]+capacity[队列大小]);通过threadPoolExecutor.submit提交Runnable接口实现的执行任务;提交第一个任务时,由于当前线程池为0且小于3(由corePoolSize指定),因此会创建一个线程来执行提交的任务1;在提交第二个和第三个任务时,由于当前线程池中正在执行的任务数小于等于3(指定corePoolSize),所以会为每个提交的任务创建一个线程来执行该任务;当提交第四个任务时,由于当前正在执行的任务数为3(因为每个线程任务的执行时间为10s,所以当第四个任务提交时,前三个线程还在执行),此时,第四个任务会被存入workQueue队列中等待执行;由于workQueue队列的大小是2,所以队列中也只能保存2个等待执行的任务,所以任务队列中也会保存第5个任务;当第6个任务提交时,由于线程池当前正在执行的任务数为3,workQueue队列中存储的任务数也已满。此时会判断当前线程池中正在执行的任务数是否小于6(由maximumPoolSize指定);如果小于6,则创建一个新的线程来执行提交的任务6;执行第7步,当有8个任务时,还需要判断当前线程池中正在执行的任务数是否小于6(由maximumPoolSize指定)。如果小于6,则立即创建一个新的线程来执行这些提交的任务;此时,6个任务都已经提交完毕,那workQueue队列中等待的任务4和任务5什么时候执行呢?当任务1执行完毕(10s后),执行任务1的线程并没有被销毁,而是拿到workQueue中的任务4执行;当任务2执行时,执行任务2的线程也没有被销毁。取而代之的是让workQueue中的任务5去执行;通过以上过程的分析,也可以知道前面案例输出的原因。实际上,线程池中的线程执行完后,并不会立即销毁,线程池中会保留corePoolSize个线程数。当workQueue队列中有任务或者有新的提交时,任务会通过线程池中已有的线程执行,避免了频繁的线程创建和销毁,以及那些corePoolSize大于等于maximumPoolSize时创建的线程空闲指定时间(keepAliveTime)后会被回收。5.ThreadPoolExecutor拒绝策略在上面的测试中,我设置的执行线程总数恰好等于maximumPoolSize[最大线程数]+capacity[队列大小],所以不需要执行拒绝策略,所以这里,我又加了一个线程,提交9个任务,来演示不同的拒绝策略。AbortPolicyCallerRunsPolicyDiscardPolicyDiscardOldestPolicy