Java线程池学习

166 阅读5分钟

线程池参数

线程数参数

  1. 如果线程数小于corePoolSize,即使其他工作线程处于空闲,也会创建一个新线程去执行任务
  2. 如果线程数大于corePoolSize小于maximumPoolSize,则将线程放入到队列(workQueue)中
  3. 如果队列已满,但是线程数小于maximumPoolSize,则创建一个新线程来运行任务
  4. 如果队列已满,并且线程大于或等于maximumPoolSize,则拒绝该任务

空闲存活时间参数

keepAliveTime(存活时间)参数:表示非核心线程空闲存活时间

如果设置了allowCoreThreadTimeout=true,则线程池线程数小于核心线程数也会在空闲时间大于设置的keepAliveTime的范围内被回收,直到线程数为0

线程工厂

新的线程由线程工厂(ThreadFactory)创建的,默认使用Executors中的DefaultThreadFactory静态内部类进行创建的,创建的线程默认都在同一个线程组,拥有同样的优先级NORM_PRIORITY并且都不是守护线程,如果自己指定ThreadFactory,那么就可以改变这些参数

工作队列

workQueue有三种常见的队列类型

  • 直接交接:SynchronousQueue

    该队列没有内存空间,只是做一个中转的作用

  • 无界队列:LinkedBlockingQueue

    该队列可以一直存放队列,可能会造成内存浪费或者OOM异常

  • 有界队列:ArrayBlockingQueue可以设置队列大小

增减线程特点

  • 通过设置corePoolSize和maximumPoolSize相等,就可以创建一个大小固定的线程池
  • 线程池希望保持很小的线程数,并且只有在负载变得很大的时候才增加它
  • 通过设置maximumPoolSize为一个很高的值,比如Integer.MAX_VALUE,可以允许线程池容纳任意数量的并发任务
  • 如果你使用的队列是无界队列(例如LinkedBlockingQueue),那么线程数就不会超过corePoolSize

线程池创建方式

自动创建

JDK有几个自动创建线程池的方法

  • Executors.newFixedThreadPool(6):固定线程池,设置了核心线程数与最大线程数一致,队列使用无界队列,当请求发生堆积的时候可能会导致OOM

  • Executors.newSingleThreadExecutor():单个线程池,跟上面的创建一致,只是默认该方式默认了线程数为1

  • Executors.newCachedThreadPool():缓存线程池,设置的核心线程数为0,最大线程数为Integer.MAX_VALUE,使用的队列为直接队列,当任务过多时,会导致创建了特别多的线程,可能会导致OOM

  • Executors.newScheduledThreadPool(10):支持定时及周期性任务执行的线程池,最大线程数为Integer.MAX_VALUE,使用的队列为优先队列DelayedWorkQueue,由于设置了最大线程数为Integer.MAX_VALUE,所以任务过多时也有可能导致OOM

    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
    //第一个参数是任务,第二个参数是第一次执行在多少秒之后,第三个参数是以后每次多久执行一次,第四个参数是时间单位
    executorService.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            System.out.println("测试!");
        }
    }, 10, 2, TimeUnit.MILLISECONDS);
    

线程手动创建

线程手动创建最优,可以根据不同的场景设置适合的参数。

  • CPU密集型(加密,计算hash):最佳线程数是CPU核心数的一到两倍
  • 耗时IO型(读写数据库,文件,网络读写等),最佳的线程数一般会大于cpu核心数的许多倍,参考计算公式:线程数=CPU核心数*(1+平均等待时间(读写数据库)/平均工作时间),最佳的方式通过压测来判断需要多少线程能够达到最优

线程池停止

  • shutdown():该方法用于停止线程池中的线程,但是它不是立即停止,而是将队列中的任务和正在运行的任务执行完成并且在这个过程中将不再接受新的任务,当有新的任务提交时会报错。
  • isShutdown():该方法用来判断线程池是否存在终止标记,这个为true并不代表线程已经终止了,而是表示线程池处于终止过程中
  • isTerminated():该方法就是用来判断线程池中的线程是否已经终止
  • awaitTermination(5L,TimeUnit.MILLISECONDS):该方法用来表示给定时间的时候线程是否已经终止了,只是做判断不会对线程有任何操作,该方法在返回之前会阻塞线程,阻塞解除的方式:1:在时间之前线程已经结束了,2:时间到了,3:线程被打断了
  • shutdownNow():该方法会立即停止线程,对于正在运行的线程使用interrupted()方法停止,并且将队列中的任务返回,不执行

拒绝策略

当工作队列已经满了,并且最大线程数已经满了,将执行拒绝策略

  • AbortPolicy:直接抛出一个异常
  • DiscardPolicy:默默的丢弃,程序不知道被丢弃了
  • DiscardOldestPolicy:丢弃最老的任务
  • CallerRunsPolicy:饱和状态下,让提交任务的线程去执行

钩子方法

通过集成ThreadPoolExecutor来重写下面的两个方法

beforeExecute:在线程执行之前进行的处理逻辑

afterExecute:在线程执行之后执行的处理逻辑

线程池原理

Executor家族

  • Executors类是一个工具类,用来快速创建线程池

线程池的组成

  • 线程池管理器
  • 工作队列
  • 任务队列
  • 任务接口(Task)

实现线程复用

使用相同的线程去执行不同的任务

线程池的状态

  • RUNNING:接受任务并处理任务
  • SHUTDOWN:不接受新任务,但是执行队列中的任务
  • STOP:既不接受新任务,也不处理排队任务,并中断正在执行的任务
  • TIDYING:所有的任务都已经终止了,workerCount清零时,线程会转换到TIDYING状态,并将执行terminate()钩子方法
  • TERMINATED:terminate()运行完成