线程池Executor 框架

基础部分:

线程状态

  1. 初始状态 : 实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态

  2. 可运行(就绪)状态 : 只是说你资格运行,调度程序没有挑选到你,你就永远是可运行状态。

    ​ 调用线程的start()方法,此线程进入可运行状态。

    ​ 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入可运行状态。

    ​ 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入可运行状态。

    ​ 锁池里的线程拿到对象锁后,进入可运行状态。

  3. 运行状态 : 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

  4. 死亡状态 : 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。

    ​ 在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

  5. 阻塞状态 : 当前线程T调用Thread.sleep()方法,当前线程进入阻塞状态。

    ​ 运行在当前线程里的其它线程t2调用join()方法,当前线程进入阻塞状态。

    ​ 等待用户输入的时候,当前线程进入阻塞状态。

等待队列&锁池

  • 相关方法

    1. Object :

      ​ wait() 进入等待队列

      ​ notify() 唤醒等待队列,进入锁池

      ​ notifyAll() 唤醒所有等待队列,进入锁池

    2. Thread :

      ​ sleep() 当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。

      ​ yield() 当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。

      ​ join() 当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行 完毕或者millis时间到,当前线程进入可运行状态。

      ​ start() 即启动了线程 , 新创建的线程并不自动开始运行 , 就处于可运行状态

      ​ 不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException

  • Synchronized JDK1.6 之后的底层优化

    JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

    锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

ThreadPoolExecutor 类

ThreadPoolExecutor 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么)。

/**
     * 用给定的初始参数创建一个新的ThreadPoolExecutor。
     */
    public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                              int maximumPoolSize,//线程池的最大线程数
                              long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
                              ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
                              RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
                               ) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

下面这些对创建 非常重要,在后面使用线程池的过程中你一定会用到!

Runtime.getRuntime().availableProcessors(); //获取cpu核心数,期望值

ThreadPoolExecutor 3 个最重要的参数:

corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。

maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

ThreadPoolExecutor其他常见参数:

keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;

unit : keepAliveTime 参数的时间单位。

threadFactory :executor 创建新线程的时候会用到。

handler :饱和策略。关于饱和策略下面单独介绍一下。

下面这张图可以加深你对线程池中各个参数的相互关系的理解:

ThreadPoolExecutor 饱和策略定义:

如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略:

ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。

ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序
可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。

ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。

ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。

Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使用的是 ThreadPoolExecutor.AbortPolicy。在默认情况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 ThreadPoolExecutor 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了。)

常用线程池

  • newCachedThreadPool :

    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    这种类型的线程池特点是:

    • 工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
    • 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
    • 在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
  • newFixedThreadPool:

    创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

    FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

  • newSingleThreadExecutor:

    创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

  • newScheduleThreadPool:

    创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

  • ThreadPoolTaskExecutor (可深入了解一下):

    Spring框架提供的线程池技术。可配合异步注解使用, 底层是基于JDK的ThreadPoolExecutor实现。

    • 创建及使用
    //创建
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(5);//核心线程大小
            executor.setMaxPoolSize(10);//最大线程大小
            executor.setQueueCapacity(100);//队列最大容量
            executor.setKeepAliveSeconds(3000);//存活时间
            executor.setRejectedExecutionHandler(new 		     	                   							ThreadPoolExecutor.CallerRunsPolicy());//拒绝执行时如何处理
    
    //-------------------------------------------------------------------------------
    //使用
    executor.submit(new ThreadDemo());//或者executor.execute(new ThreadDemo());
    // ----------------------------
    public class ThreadDemo implements Runnable {
         @Override
         public void run() {
             //业务处理
         }
     }
    
    • 执行过程

      3.1 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

      3.2 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

      3.3 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。

      4.4 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

      当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

    • 拒绝处理

      ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
      ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
      ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面。
      ThreadPoolExecutor.CallerRunsPolicy:由调用者处理该任务 。

    • submit和execute区别

      1. 接收的参数不一样
      2. submit有返回值,execute没有返回值
      3. submit异常处理(future.get())

内容部分摘录至:JavaGuide

cpu个数、核数、线程数的关系

cpu个数:是指物理上,也及硬件上的核心数;

核数:是逻辑上的,简单理解为逻辑上模拟出的核心数;

线程数:是同一时刻设备能并行执行的程序个数,线程数=cpu个数 * 核数

# 总核数 = 物理CPU个数 X 每颗物理CPU的核数 
       # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数

       # 查看物理CPU个数
       cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

       # 查看每个物理CPU中core的个数(即核数)
       cat /proc/cpuinfo| grep "cpu cores"| uniq

       # 查看逻辑CPU的个数
       cat /proc/cpuinfo| grep "processor"| wc -l

       #查看线程数                                                                                                                                                                                                       
       grep 'processor'        /proc/cpuinfo | sort -u | wc -l      
       #注意,此处查看的线程数是总得线程数,可以理解为逻辑cpu的数量

线程池创建:
https://blog.csdn.net/trusause/article/details/125747447

posted @ 2020-10-23 12:18  栋_RevoL  阅读(50)  评论(0编辑  收藏  举报