JAVA线程池

  • 1、什么是线程池

这里先简述一下,要执行一个线程(任务)时,不是new一个线程,而是从线程池获取一个存在的线程,执行完毕后,把该线程归还到线程池,等待下次执行任务。

  • 2、线程池的原理

线程池是一个对象池。对象池是一种常见的系统优化技术。其核心思想是,一个类被频繁使用时,没必要每次都初始化一个对象,可以将它的一些实例保存在一个容器中,使用时向容器请求,用完后归还容器,达到复用对象的目的,减少对象创建和销毁时的系统消耗。这里的容器就是对象池。线程池本质上是一个线程对象的对象池,由于对象的创建和销毁会有一定的耗时,使用线程池复用线程有利于提高系统性能。

对象池的使用场景:一些大型对象被频繁调用,大型对象指创建或销毁比较耗时的对象

常见的对象池有:线程池,数据库连接池。

  • 3、线程池的好处

1)降低资源消耗

从对象池的知识我们知道,线程池降低了线程创建和销毁的资源消耗

2)提高响应速度

线程池减少了创建和销毁线程池的时间,提高了响应速度

3)便于管理线程

线程池提供了关闭线程池、指定核心线程数等操作,方便了管理线程。

  • 4、默认实现

JAVA给我们定义好了几种常见的线程池:

1)生成固定线程数的线程池

     Executors.newFixedThreadPool(int nThreads)

2)生成一个可缓存的线程池

     Executors.newCachedThreadPool()

3)生成单线程的线程池

     Executors.newSingleThreadExecutor()

  • 5、线程池(Exector架构)的类图

JAVA线程池_第1张图片

核心类是ThreadPoolExecutor类,继承AbstractExecutorService抽象类(写了线程池的一些默认实现),AbstractExecutorService实现了ExecutorService接口(该接口定义了线程池的操作关闭、立即关闭、提交等操作),ExecutorService接口继承自Executor接口(定义execute方法)。

Executors类,在这里是工厂类。

 

 

  • 6、ThreadPoolExecutor类构造方法参数(引用自参考1JAVA线程池_第2张图片
  • corePoolSize:线程池中的核心线程数;
  • maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:线程池中非核心线程闲置超时时长(准确来说应该是没有任务执行时的回收时间);

          一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉

          如果设置allowCoreThreadTimeOut(boolean value),则也会作用于核心线程

  • TimeUnit:时间单位。可选的单位有分钟(MINUTES),秒(SECONDS),毫秒(MILLISECONDS) 等;
  •  workQueue:任务的阻塞队列,缓存将要执行的Runnable任务,由各线程轮询该任务队列获取任务执行。可以选择以下几个阻塞队列。

       ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
       LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于              ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
         SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处 于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
        PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

  • ThreadFactory:线程创建的工厂。可以进行一些属性设置,比如线程名,优先级等等,有默认实现。
  • RejectedExecutionHandler:任务拒绝策略,当运行线程数已达到maximumPoolSize,队列也已经装满时会调用该参数拒绝任务,默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。

      AbortPolicy:直接抛出异常。
      CallerRunsPolicy:只用调用者所在线程来运行任务。
      DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
      DiscardPolicy:不处理,丢弃掉。
       当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

  • 7、ThreadPoolExecutor执行过程(参考文献2)

        corePoolSize->workQueue->maxPoolSize

       1) 当未超过核心线程数时,直接创建一个核心线程去执行任务

       2) 当超过核心线程数时,将任务加入workQueue中等待执行

       3) 当workQueue满时,在不超过maxPoolSize的情况下,启动线程去处理任务

       4、当线程数量超过maxPoolSize时,调用拒绝策略

      以下是流程图:

JAVA线程池_第3张图片

 

8、理解默认线程池实现

1) FixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }

FixedThreadPool,创建固定核心线程数的线程池。提交任务时,nThread

2)CachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

CachedThreadPool,corePoolSize=0,maxPoolSize=MAX_Value。提交任务时,nThread永远>corePoolSize,同时SynchronousQueue是一个不存储元素的阻塞队列,所以CachedThreadPool提交任务时会尝试获取线程,若获取到则执行,若获取不到,则创建线程执行任务。任务执行完毕后,线程归还线程池,在无调用的情况下,存活时间是60s。

3)SingleThreadPool

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

SingleThreadPool,corePoolSize=1,maxPoolSize=1。该线程池只有一个核心线程,最多允许一个线程。若任务正在执行,新提交的任务将会进入阻塞队列等待执行。

 

  • 9、配置线程池大小

1)CUP密集型任务,就需要压榨CUP,N=Ncpu+1

2)IO密集型任务,参考值可为 N=2xNcpu

以上仅是参考值,最佳大小需要自己调试。

 

参考文献:

文献1:https://blog.csdn.net/u010983881/article/details/79322499

文献2:https://www.jianshu.com/p/e66e9924a953

文献3:https://www.cnblogs.com/dolphin0520/p/3932921.html

文献4:《JAVA程序性能优化》-葛一鸣

你可能感兴趣的:(多线程)