• 线程池的使用


    1.线程池使用场景


    java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,涉及到的几个核心类及接口包括:Executor、Executors、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable等。

    线程池可以:

    加快请求响应(响应时间优先)

    加快处理大任务(吞吐量优先)

    使用过程

    1. //创建线程池
    2. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 30, TimeUnit.SECONDS, new SynchronousQueue<>());
    3. //向线程池提交任务 无返回值
    4. threadPoolExecutor.execute(new Runnable() {
    5. @Override
    6. public void run() {
    7. }
    8. });
    9. //向线程池提交任务 有返回值
    10. Future<?> submit = threadPoolExecutor.submit(new Runnable() {
    11. @Override
    12. public void run() {
    13. }
    14. });
    15. //返回任务的执行结果
    16. try {
    17. Object o = submit.get();
    18. } catch (InterruptedException e) {
    19. e.printStackTrace();
    20. } catch (ExecutionException e) {
    21. e.printStackTrace();
    22. }
    23. threadPoolExecutor.shutdown();

    线程池的创建

    线程池可以自动创建也可以手动创建,

    自动创建

    自动创建体现在Executors工具类中,常见的可以创建newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool;

    1. //自动创建不同类型的线程池,使用Executors类,指定的参数比较少
    2. //手动创建使用 ThreadPoolExecutor ,可以指定多个参数
    3. //创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
    4. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    5. scheduledExecutorService.schedule(new Runnable() {
    6. @Override
    7. public void run() {
    8. System.out.println("每隔四秒执行");
    9. }
    10. },4, TimeUnit.SECONDS);
    11. scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    12. @Override
    13. public void run() {
    14. System.out.println("延迟1S后,每隔3秒执行一");
    15. }
    16. },1,3,TimeUnit.SECONDS);
    17. //返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)
    18. // 重新启动一个线程来替代原来的线程继续执行下去
    19. ExecutorService executorService = Executors.newSingleThreadExecutor();
    20. //创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
    21. ExecutorService executorService1 = Executors.newFixedThreadPool(3);
    22. //创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们
    23. ExecutorService executorService2 = Executors.newCachedThreadPool();

    手动创建

    手动创建体现在可以灵活设置线程池的各个参数,体现在代码中即ThreadPoolExecutor类构造器上各个实参的不同:

    1. //手动创建线程池
    2. /*
    3. corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
    4. maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
    5. keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
    6. unit:keepAliveTime的时间单位
    7. workQueue:用于保存任务的队列,可以为无界、有界、同步移交new SynchronousQueue<>()三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
    8. threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
    9. handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
    10. */
    11. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 10, 30, TimeUnit.SECONDS, new SynchronousQueue<>());


     

    workQueue队列

    SynchronousQueue(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零
    LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致内存占用过多或OOM
    ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务

     handler拒绝策略

    • AbortPolicy:中断抛出异常
    • DiscardPolicy:默默丢弃任务,不进行任何通知
    • DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
    • CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

    关闭线程池

    shutdownNow():立即关闭线程池(暴力),正在执行中的及队列中的任务会被中断,同时该方法会返回被中断的队列中的任务列表
    shutdown():平滑关闭线程池,正在执行中的及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略
    isTerminated():当正在执行的任务及对列中的任务全部都执行(清空)完就会返回true

    .线程池线程复用原理


        1.线程池里执行的是任务,核心逻辑在ThreadPoolExecutor类的execute方法中,同时ThreadPoolExecutor中维护了HashSet<Worker> workers;
        2.addWorker()方法来创建线程执行任务,如果是核心线程的任务,会赋值给Worker的firstTask属性;
        3.Worker实现了Runnable,本质上也是任务,核心在run()方法里;
        4.run()方法的执行核心runWorker(),自旋拿任务while (task != null || (task = getTask()) != null)),task是核心线程Worker的firstTask或者getTask();
        5.getTask()的核心逻辑:
                1.若当前工作线程数量大于核心线程数->说明此线程是非核心工作线程,通过poll()拿任务,未拿到任务即getTask()返回null,然后会在processWorkerExit(w, completedAbruptly)方法释放掉这个非核心工作线程的引用;
                2.若当前工作线程数量小于核心线程数->说明此时线程是核心工作线程,通过take()拿任务
                3.take()方式取任务,如果队列中没有任务了会调用await()阻塞当前线程,直到新任务到来,所以核心工作线程不会被回收; 当执行execute方法里的workQueue.offer(command)时会调用Condition.singal()方法唤醒一个之前阻塞的线程,这样核心线程即可复用

     Callable和Runnable


    Runnable和Callable都可以理解为任务,里面封装着任务的具体逻辑,提交给线程池执行,区别在于Runnable任务执行没有返回值,且Runnable任务逻辑中不能通过throws抛出cheched异常(但是可以try catch),而Callable可以获取到任务的执行结果返回值且抛出checked异常。

    Future和FutureTask


    Future接口用来表示执行异步任务的结果存储器,当一个任务的执行时间过长就可以采用这种方式:把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果。

    Future的主要方法包括:

    get()方法:返回任务的执行结果,若任务还未执行完,则会一直阻塞直到完成为止,如果执行过程中发生异常,则抛出异常,但是主线程是感知不到并且不受影响的,除非调用get()方法进行获取结果则会抛出ExecutionException异常;


    get(long timeout, TimeUnit unit):在指定时间内返回任务的执行结果,超时未返回会抛出TimeoutException,这个时候需要显式的取消任务;


    cancel(boolean mayInterruptIfRunning):取消任务,boolean类型入参表示如果任务正在运行中是否强制中断;


    isDone():判断任务是否执行完毕,执行完毕不代表任务一定成功执行,比如任务执行失败但也执行完毕、任务被中断了也执行完毕都会返回true,它仅仅表示一种状态说后面任务不会再执行了;


    isCancelled():判断任务是否被取消;
     

  • 相关阅读:
    No151.精选前端面试题,享受每天的挑战和学习
    第十三届蓝桥杯省赛C/C++ B组
    拖拽式在线表单设计器好用吗?
    【服务的主从切换实现原理】
    2023亚太杯数学建模思路 - 案例:ID3-决策树分类算法
    【机器学习】线性分类【下】经典线性分类算法
    解决mvn常用指令build failure的问题
    Dubbo3应用开发—Dubbo服务管理平台DubboAdmin介绍、安装、测试
    JavaScript学习Day001
    【设计模式】十、组合模式
  • 原文地址:https://blog.csdn.net/xiaowang_lj/article/details/125612743