Weixl 的个人博客

贼拉正经的个人博客

  menu
22 文章
0 浏览
2 当前访客
ღゝ◡╹)ノ❤️

线程池

线程池

前言

  • 线程是程序执行的最小单位,是实际运作的单位。
  • 没有启动的软件成为程序,整个程序启动后会产生一个进程,而一个进程中可能会有多个线程分别执行自己的任务。
  • 线程池 和 数据库的连接池 等是一个概念,就是为了避免频繁创建对象,销毁对象来进行无意义的浪费资源。
  • 学习文章:线程池

线程生命周期

1603025646082.png

  1. 第一步通过 New 来创建一个线程对象
  2. 创建完线程对象后 .start() 启动后并不是直接运行,而是进入就绪状态,等待CPU的调度来执行
  3. cpu将资源给到 就绪状态 后就会 运行线程任务,到达运行状态,使用CPU资源来执行任务
  4. 运行状态 之后 如果任务完美执行完毕没有被 其他 线程 抢去CPU资源,就会进入 死亡状态,等待垃圾回收
  5. 运行状态 后如果任务中有 .sleep() 或 .wait() 进行休眠,那么就会进入 阻塞状态 , CPU资源会被其他线程抢走。
  6. 阻塞状态后,就会进入就绪状态,等待CPU重新调度资源,来执行未执行完的任务。

ThreadPoolExecutor

  • java提供了一个创建线程类的 接口: Executor
  • ThreadPoolExecutor是实现了 Executor , 来创建线程池对象
    public ThreadPoolExecutor(int corePoolSize,  
                                  int maximumPoolSize,  
                                  long keepAliveTime,  
                                  TimeUnit unit,  
                                  BlockingQueue<Runnable> workQueue,  
                                  ThreadFactory threadFactory,  
                                  RejectedExecutionHandler handler)
    
  • 线程池运行流程1603027604589.png

构造参数

1603026250738.png

  • corePoolSize:创建线程池的核心线程数量,该线程数不会被回收

    当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。
    
    调用了prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
    
  • maxnumPoolSize:线程池最大可以容纳的线程数量

    线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没用了。
    
  • KeepAliveTIme:线程池中除去 核心线程外,其它线程的存活时间

    线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程利用率。
    
  • TimeUnit: 时间单位

    可选的单位有天(Days)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)
    
  • workQueue: 等待队列,当任务太多,线程数不够时,就会给放到队列中,等待被执行,执行FIFO原则(先进先出)

    用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:
    ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO原则排序
    
    LinkedBlockingQueue:基于链表结构的阻塞队列,吞吐量高于ArrayBlockingQUeue,静态工厂方法 Excutors.newFixedThreadPool() 使用这个队列
    
    SynchronousQueue: 不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量高于LinkedBlockingQueue,静态工厂方法Excutors.newCachedThreadPool()使用了这个队列
    
    PriorityBlockingQueue: 一个具有优先级的无限阻塞队列。
    
  • threadFactory:创建线程的线程工厂,可以进行一些定制操作

    可以通过线程工厂为每个创建出来的线程设置更有意义的名字,如开源框架guava
    
  • RejectedExecutionHandler: 拒绝策略,当任务满了,完全放不下时执行的拒绝手段

    当前线程数量饱和了,采用一种策略来处理新提交的任务:
    
    AbortPolicy:直接抛出异常,默认情况下采用这种策略
    CallerRunsPolicy:只用调用者所在线程来运行任务
    DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
    DiscardPolicy:不处理,丢弃掉
    

创建代码

  • 创建线程池,可以创建THreadPoolExecutor对象来创建, 也可以使用Executors 的工厂方法来创建

  • ThreadPoolExecutor:

    public ThreadPoolExecutor threadPoolExecutor() {
            // 创建一个 队列
            LinkedBlockingQueue queue = new LinkedBlockingQueue<>(10);
    
            // 核心线程4个 最大线程数8  非核心线程存活时间300  TimeUnit时间单位   Queue超过线程数存储到什么队列中
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 8, 300L, TimeUnit.SECONDS, queue);
    
            // 设置线程工程,自定义一下
            threadPoolExecutor.setThreadFactory(new ThreadFactory() {
                @Override
                public Thread newThread(@NotNull Runnable r) {
                    // ThreadFactory工厂 , 可以对 线程任务分发线程,进行一些特定行为:取名称..等
                    return new Thread(r , "任务class名称:---" + r.getClass().getName());
                }
            });
    
            // 设置内存饱满处理机制
            // hreadPoolExecutor.AbortPolicy() 是一个内部类 , 所以这个new 是创建的 AbortPolicy 对象
            threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    
            // 创建线程池
            return threadPoolExecutor;
        }
    
  • 使用Executors 静态工厂方法来创建:

    CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
    
    SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
    
    SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
    
    FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
    
    // 创建方法
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    

启动任务

  • 启动线程任务有两种方法:
  • execute方法用于提交不需要返回值的任务
threadPoolExecutor.execute(new Runnable() {
          
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
  • submit方法用于提交一个任务并带有返回值,这个方法将返回一个Future类型对象。可以通过 这个返回对象判断任务是否执行成功,并且可以通过future.get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成。
Future<?> future=threadPoolExecutor.submit(futureTask);
      
        Object value=future.get();

关闭线程池

  • 有两个关闭线程池的方法
  • shutdown或shutdownNow方法 :原理都是遍历线程池中的工作线程,然后逐个调用线程的 interrup() 方法来中断线程,所以无响应中断的任务可能永远无法停止。
  • shutdownNow首先将线程池的状态设置为STOP,然后尝试立刻停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表
    
  • shutdown只是将线程池的状态设置成SHUTDOWN状态,然后慢慢等任务执行完后中断所有正在执行的任务。
    
  • 只要调用了这两个关闭方法的一个,isShutdown就会返回true。当所有的任务都关闭后,才表示线程池关闭成功,这是调用isTerminated方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定执行完,则可以调用shutdownNow方法。
    

合理配置线程池


标题:线程池
作者:Weixl
地址:http://loveless.top/articles/2020/10/18/1603028507368.html