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

线程池基本介绍和使用

时间:2023-04-02 01:46:57 Java

线程池基本介绍和使用我们知道,在Java中,创建对象只是在JVM堆中分配一块内存;创建线程需要调用操作系统内核的API,然后操作系统要为线程分配一系列的资源,开销很大,所以线程是重量级对象,应该避免频繁的创建和销毁.因此Java提供了线程池,它本质上是一个容纳多个线程的容器。里面的线程是可以重复使用的,省去了频繁创建线程对象,不重复创建线程消耗太多资源的麻烦。JDK线程池相关类JDK中提供的线程池相关类及其关系如下图所示:Executor接口:线程池的抽象接口,只包含一个execute方法。ExecutorService子接口:提供了一些终止线程池和Future返回值的方法。AbstractExecutorService抽象类:提供了一些ExecutorService的默认实现。ThreadPoolExecutor类:JDK提供的线程池的实现类。Executors类:线程池工厂类,提供了几个线程池工厂方法。下面主要介绍JDK提供的线程池实现类——ThreadPoolExecutor类。在Java提供的线程池相关工具类中,ThreadPoolExecutor是ThreadPoolExecutor的核心。构造方法ThreadPoolExecutor的构造函数非常复杂,如下代码所示,最完整的构造函数有7个参数。ThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)下面一一介绍参数的含义。corePoolSize默认情况下,创建线程池后,线程池中的线程数为0。当有任务到来时,会创建一个线程来执行该任务。当线程池中的线程数达到corePoolSize时,线程就会停止。创建后,依次将任务放入任务队列等待。调用prestartAllCoreThreads()或prestartCoreThread()方法预创建线程,即在没有任务到来之前创建corePoolSize线程或maxPoolSize线程。当线程数大于或等于核心线程数且任务队列已满时,将创建线程池。新线程直到线程数达到maxPoolSize。如果线程数等于maxPoolSize,任务队列已满,说明已经超过线程池的处理能力,线程池将拒绝处理任务并抛出异常。keepAliveTime&unit当线程空闲时间达到keepAliveTime,unitunit时,线程将退出,直到线程数等于corePoolSize。workQueue任务队列,一个阻塞队列,用于存放等待执行的任务。建议workQueue不要使用无界队列,尽量使用有界队列。避免等待大量任务,导致OOM。支持有界的阻塞队列有ArrayBlockingQueue和LinkedBlockingQueue。threadFactory线程工厂,通过这个参数你可以自定义如何创建线程,比如你可以给线程起一个有意义的名字。处理程序拒绝该策略。如果线程池中的所有线程都处于忙碌状态,并且工作队列已满(前提是工作队列是有界队列),那么线程池将拒绝接受此时提交的任务,拒绝可以指定通过处理程序参数Strategy。ThreadPoolExecutor提供了以下4种策略:ThreadPoolExecutor.AbortPolicy:默认的拒绝策略。任务被丢弃并抛出RejectedExecutionException。ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常。ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中等待时间最长的任务,然后再次尝试执行该任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy:直接在execute方法的调用线程中运行被拒绝的任务。提交任务的常用方法方法voidexecute(Runnablecommand):提交任务到线程池执行,任务无返回值Futuresubmit(Runnabletask):由于Runnable接口没有返回值,Future返回值是执行get()方法返回的值为null,函数只是等待,类似joinFuturesubmit(Runnabletask,Tresult):由于Runnable没有返回值,提供了一个附加参数作为返回值。Futuresubmit(Callabletask):将任务提交到线程池中执行,并返回执行结果。其他方法voidallowCoreThreadTimeOut(booleanvalue):是否允许核心线程超时,默认为false。shutdown():关闭线程池,等待任务执行完shutdownNow():关闭线程池,不等待任务执行完,返回等待执行的任务列表。getTaskCount():线程池中已执行和未执行的任务总数getCompletedTaskCount():已完成的任务数getPoolSize():线程池中当前的线程数线程池任务的大致执行流程图如图下图:线程池初始化示例下面是线程池初始化示例,仅供参考%d").build();pool=newThreadPoolExecutor(4,8,60L,TimeUnit.MILLISECONDS,newLinkedBlockingQueue<>(512),threadFactory,newThreadPoolExecutor.AbortPolicy());pool.allowCoreThreadTimeOut(true);}初始化参数含义解释:threadFactory:以业务语义命名线程。corePoolSize:快速启动4个线程处理业务就够了。maximumPoolSize:IO密集型业务,我的服务器是4C8G,所以4*2=8。keepAliveTime:服务器资源紧张,可以快速释放空闲线程。pool.allowCoreThreadTimeOut(true):也是让线程尽可能释放和释放资源。workQueue:一个任务执行时间100~300ms,业务高峰期有8个线程,超时10s(已经很高了)。10秒,8个线程,可以处理101000ms/200ms8=大约400个任务,多拿一点,512已经很多了。handler:在极端情况下,有些任务只能丢弃,以保护服务器。使用线程池注意事项避免使用Executors类创建线程池,有OOM风险。在创建线程或线程池时,请指定一个有意义的线程名,方便出错时回溯。即必须构造threadFactory参数。建议不同类型的业务使用不同的线程池。至于线程池的数量,各自计算,然后进行压力测试。workQueue不要使用无界队列,尽量使用有界队列。避免等待大量任务,导致OOM。支持有界的阻塞队列有ArrayBlockingQueue和LinkedBlockingQueue。如果是资源紧张的应用,使用allowsCoreThreadTimeOut可以提高资源利用率。虽然使用线程池处理异常的方法有很多,但是在任务代码中使用try-catch是最常用的方法,也可以细化不同任务的异常处理。线程池默认的拒绝策略会抛出RejectedExecutionException,这是一个运行时异常。编译器不会强制捕获运行时异常,因此开发人员可以轻松忽略它。因此,应谨慎使用默认拒绝策略。如果线程池处理的任务很重要,建议自定义拒绝策略;并且在实际工作中,自定义拒绝策略常常与降级策略结合使用。对于CPU密集型任务,最大线程数的初始值可以配置为N+1。对于I/O密集型任务,最大线程数的初始值可以配置为2N。之后,可以根据压力测量进行调整。参考10问题10答案:你真的了解线程池吗?图|你管这狗屎叫线程池?执行器和线程池:如何创建正确的线程池?-极客时间