当前位置:   article > 正文

线程池用法详解_newsinglethreadscheduledexecutor

newsinglethreadscheduledexecutor

线程池用法详解

前言

为什么需要用到线程池?

众所周知线程是操作系统一种宝贵的资源,创建和销毁都很耗费资源,对此,java中针对这种情况实现出线程池思想,维护一定数量的线程,用到时候就不需要去创建,不使用时候,就销毁一定量的,保留一部分线程数量。

Java线程池的工作流程

Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。

  • 如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务。

  • 如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中。

  • 在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务。

  • 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。

  • 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。

  • 在线程处于空闲状态的时间超过keepAliveTime时间时,正在运行 的线程数量超过corePoolSize,该线程将会被认定为空闲线程并停止。因 此在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。

在这里插入图片描述

5种常用的线程池

JAVA中提供了5种常用的线程池

缓存线程池 newCachedThreadPool

用于创建一个缓存线程池。之所以叫缓存线程 池,是因为它在创建新线程时如果有可重用的线程,则重用它们,否则重 新创建一个新的线程并将其添加到线程池中。对于执行时间很短的任务而言,newCachedThreadPool线程池能很大程度地重用线程进而提高系统的性能。

在这里插入图片描述

适用场景:

执行时间很短的任务

特点:

执行时间很短的任务,无容量队列,提交一个任务创建一个线程。

使用 SynchronousQueue队列,特点是没有容量,没有线程是放不进去的。

没有线程任务运行时,newCachedThreadPool将不会占用系统的线程资源。

固定线程池newFixedThreadPool

在这里插入图片描述

特点:

核心线程数 == 最大线程数,无需超时销毁。
阻塞队列是无界的,可以存放任意数量的任务。

适用场景:

适用于任务量已知,相对耗时的任务。

可定时调度 newScheduledThreadPool

newScheduledThreadPool创建了一个可定时调度的线程池,可设置在给定的延迟时间后执行或者定期执行某个线程任务

在这里插入图片描述
构造
在这里插入图片描述

延迟执行任务
在这里插入图片描述
按固定频率执行,与任务本身执行时间无关。但 有个前提条件,任务执行时间必须小于间隔时间,例如间隔时间是5 s,每5s执行一次任务,任务的执行时间无关,即使任务执行大于5s,每次间隔执行,也会等待上次执行结束才会执行。

周期执行任务
在这里插入图片描述

WithFixedDelay:按固定间隔执行,与任务本身执行时间有 关。例如,任务本身执行时间是10s,间隔2s,则下一次开始执行的
时间就是12s。

newSingleThreadScheduledExecutor 单个线程任务调度

newSingleThreadExecutor线程池会保证永远有且只有一个可用的线程,在该线程停止或发生异常时,newSingleThreadExecutor线程池会启动一个新的线程来代替该线程继续执行任务
在这里插入图片描述
相比于TimerTask 定时任务类,发生异常不会退出,不影响执行任务。

newWorkStealingPool

JDK提供的一个新的线程池newWorkStealingPool

该线程池以上线程池构造不同,采用ForkJoin线程池,并发是使用ThreadPoolExecutor构造
在这里插入图片描述

newWorkStealingPool创建持有足够线程的线程池来达到快速运算的目
的,在内部通过使用多个队列来减少各个线程调度产生的竞争。这里所说 的有足够的线程指JDK根据当前线程的运行需求向操作系统申请足够的线 程,以保障线程的快速执行,并很大程度地使用系统资源,提高并发计算
的效率,省去用户根据CPU资源估算并行度的过程。当然,如果开发者想自
己定义线程的并发数,则也可以将其作为参数传入。

   		//获取cpu核数
        // System.out.println(Runtime.getRuntime().availableProcessors());


        // 不指定线程大小 则是cpu默认核心数
        ExecutorService executorService = Executors.newWorkStealingPool();

        //可以提交 有返回 或者 无返回
        Future<String> submit = executorService.submit(() -> {
            TimeUnit.SECONDS.sleep(1);
            return "run able";
        });

        Future<String> submit1 = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {

                TimeUnit.SECONDS.sleep(1);
                return "call able ";
            }
        });
        System.out.println(submit.get());
        System.out.println(submit1.get());
        System.out.println(" ------------------- ");

        //可以  提交一个集合  会等全部线程运行结束
        //invokeAny 执行给定的任务,如果有任务成功完成,则返回成功完成的任务的结果(即,不引发异常)。在正常或异常返回时,未完成的任务将被取消。
        // List<Callable<String>> list  = new ArrayList<>();
        // executorService.invokeAny(list);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

ThreadPoolExecutor构造方法

一般情况下,构造线程使用ThreadPoolExecutor来显示的指定线程池参数,包括核心线程数,最大线程数,时间,队列选择以及大小,线程工厂,拒绝策略。防止因为默认的参数任务数量大或者频繁创建线程带来系统问题。

在这里插入图片描述

线程池中提交任务方法

在这里插入图片描述

关闭线程池
shutdown()

非阻塞方法

	//打断
    // 20 个线程
    // 10 个空闲 会打断线程 并且标记
    // 10个工作  等待工作执行完成
  • 1
  • 2
  • 3
  • 4
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
              //设置SHUTDOWN 标志 打断空闲线程,设置执行线程中断标志,等待任务运行结束
            advanceRunState(SHUTDOWN); 
            interruptIdleWorkers();  //打断空闲线程
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate(); //尝试在打断
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
shutdownNow()

非阻塞方法

	// 20个线程
    // 10个在队列里
    // 1 、会立即将队列里任务打断返回(这些不会处理完)
    // 2 、打断工作中10个线程,最后执行任务中的10个线程 等待执行完成(处理完)
    // 3 、 20个线程退出
  • 1
  • 2
  • 3
  • 4
  • 5
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);  //设置stop 标志 打断  执行和没执行的线程都打断
            interruptWorkers();  //打断线程
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;  //返回队列中任务
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  List<Runnable> runnables = executorService.shutdownNow();
  //返回任务
        System.out.println(runnables);
        System.out.println(runnables.size());
  • 1
  • 2
  • 3
  • 4
后台线程方式停止线程池

需要自定义线程池 线程工厂

   ExecutorService executorService = new ThreadPoolExecutor(10,20,30, TimeUnit.SECONDS,
               new ArrayBlockingQueue<>(10),

                //线程工厂 参数
                r ->{
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
                },new ThreadPoolExecutor.AbortPolicy());

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

自定义线程工厂,设置主线程是后台线程, 线程池等待时间 5s 同时线程组也是后台线程,整个线程池就会一起退出!

最多等待时间打断

在这里插入图片描述
设置等待时间,最多等待多少时间,没有执行完,则销毁线程池。
executorService.awaitTermination(5,TimeUnit.SECONDS);

对于非返回参数任务而言,想知道每个线程是否执行完成,这个时候,配合CountDownLatch使用。

拒绝策略

当最大线程池和队列都满的时候,此时再来任务,这时候就需要做出抉择,是抛弃任务还是怎么办。线程池中提供了4中策略。

AbortPolicy

线程池默认就是AbortPolicy直接抛出异常,阻止线程正常运行

CallerRunsPolicy

CallerRunsPolicy的拒绝策略为:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
线程池没有关闭,队列满了,则使用运行线程池的线程执行它。 如果在main里,则就是mian

DiscardOldestPolicy

移除线程队列中最早的一个线程任务,并尝试提交当前任务。

DiscardPolicy

DiscardPolicy的拒绝策略为:丢弃当前的线程任务而不做任何处理。如果系统允许在资源不足的情况下丢弃部分任务,则这将是保障系统安
全、稳定的一种很好的方案。

当然也可以自定义策略

上面四种都实现了RejectedExecutionHandler接口,若无法 满足实际需要,则用户可以自己扩展RejectedExecutionHandler接口来实 现拒绝策略,并捕获异常来实现自定义拒绝策略。

线程池异常处理

线程池异常处理

线程大小如何选择

线程池最核心的东西是线程数量选择,一般而言,线程数量选择取决去该任务是计算型还是IO型任务。

理论而言

计算型
线程数量 = CPU核心数 + 1

IO型
线程数量 = 2 * CPU核心数 + 1

IO型还有一种计算公式
线程数量 = 核心数 * 期望CPU利用率 * 总时间(CPU计算时间 + 等待时间) / CPU 计算时间

当然具体参数是需要根据实际任务和CPU处理能力压测动态调节线程大小的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/83710
推荐阅读
相关标签
  

闽ICP备14008679号