java并发编程之线程池

在这里插入图片描述

线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

  • RUNNING 正在运行
  • SHUTDOWN 不会接受新任务,但会处理阻塞队列中的任务
  • STOP 会中断当前执行的任务,并抛弃阻塞队列
  • TIDYING 任务执行完毕
  • TERMINATED 终结状态

这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值

构造方法

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
  • int corePoolSize

    常驻核心线程数,如果为 0,当执行完任务没有任何请求时会消耗线程池;如果大于 0,即使本地任务执行完,核心线程也不会被销毁。该值设置过大会浪费资源,过小会导致线程的频繁 创建与销毁。

  • int maximumPoolSize

    线程池能够容纳同时执行的线程最大数,必须大于等于 1,如果与核心线程数
    设置相同代表固定大小线程池。

  • long keepAliveTime

    超过核心线程数时闲置线程的存活时间。

  • TimeUnit unit

    keepAliveTime 的时间单位。

  • BlockingQueue workQueue

    工作队列,当线程请求数大于等于 corePoolSize 时线程会进入阻塞队列。

  • threadFactory:

    线程工厂,用来生产一组相同任务的线程。可以给线程命名,有利于分析错误。

  • handler:

拒绝策略,默认使用 AbortPolicy 丢弃任务并抛出异常,CallerRunsPolicy 表示重新尝试 提交该任务,DiscardOldestPolicy 表示抛弃队列里等待最久的任务并把当前任务加入队列,DiscardPolicy 表示直接抛弃当前任务但不抛出异常。

线程池的工作方式

1.线程池刚开始没有任务,当一个任务提交给线程池时后,线程池创建一个新线程来执行任务。

2.当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排
队,直到有空闲的线程。

3.如果队列选择了有界队列,当阻塞任务数大于队列的容量时,创建救急线程执行任务(救急线程等于maximumPoolSize-corePoolSize)。

4.如果线程数达到该线程池最大线程数(maximumPoolSize),还有任务未执行,这是会执行拒绝策略。

线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后还会循环获取工作队列中的任务来执行。当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断。如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

创建线程池的方法

可以通过 Executors 的静态工厂方法创建线程池:

newFixedThreadPool

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

固定大小的线程池,核心线程数也是最大线程数,不存在救急线程,keepAliveTime = 0。该线程池使用的工作队列是无界阻塞队列 LinkedBlockingQueue,适用于负载较重的服务器。线程不会自动结束。

newSingleThreadExecutor

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

该线程池只有一个线程,相当于单线程串行执行所有任务。任务数大于1时,其他任务会放入无界队列。所有线程执行完毕,该线程也不会释放。如果当前线程执行失败,会终止该线程,新创建一个线程继续执行任务队列的任务。适用于需要保证顺序执行任务的场景。

ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(() ->{
    log.debug("1");
    Sleep.sleep(1000);
    int i =1/0;
});
pool.execute(() ->{
    log.debug("1");
    Sleep.sleep(1000);
    //int i =1/0;
});
pool.execute(() ->{
    log.debug("1");
    Sleep.sleep(1000);
});

在这里插入图片描述

单线程线程池与单线程的区别:
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作。

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
       return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                     60L, TimeUnit.SECONDS,
                                     new SynchronousQueue<Runnable>());
    }
  • 核心线程数为0,maximumPoolSize 设置为 Integer 最大值,是高度可伸缩的线程池。也说明该线池中全部都是救急线程(空闲60s后被收回),可以被无限创建。如果线程池大小超过了处理任务所需要的线程,那么就会部分空闲线程(60s内没有执行任务)。当任务数增加时,线程池创建新线程执行任务。

  • 该线程池使用的工作队列是没有容量的 SynchronousQueue,如果主线程提交任务的速度高于线程处理
    的速度,线程池会不断创建新线程,极端情况下会创建过多线程而耗尽CPU 和内存资源。适用于执行很
    多短期异步任务的小程序或负载较轻的服务器。

newScheduledThreadPool

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

核心线程数固定,线程数最大为 Integer 最大值,存在 OOM 风险。支持定期及周期性任务执行,适用需要多个后台线程执行周期任务,同时需要限制线程数量的场景。相比 Timer 更安全,功能更强,与newCachedThreadPool 的区别是不回收工作线程。

 //任务调度线程池
 ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); 
//延时x秒执行任务
 pool.schedule(() ->{
     log.debug("task 1 is running");
     try {
         Thread.sleep(1000);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 },3, TimeUnit.SECONDS);

//延迟一秒执行任务,并每隔一秒执行一次,间隔算入线程的运行时间,
// 假如线程运行时间大于间隔时间,会在线程运行后直接下一次任务
 pool.scheduleAtFixedRate(()->{
      log.debug("task 1 is running");
      try {
              Thread.sleep(2000);
          } catch (InterruptedException e) {
               e.printStackTrace();
           }
      },1,1,TimeUnit.SECONDS);
 //延迟一秒执行任务,并需要等待任务完成,在等一秒循环执行一次,!
   pool.scheduleWithFixedDelay(() ->{
          log.debug("task 1 is running");
          try {
                Thread.sleep(2000);
           } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },1,1,TimeUnit.SECONDS);
    }

线程提交任务的方法

// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;


// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;

关闭线程池

可以调用 shutdown 或 shutdownNow 方法关闭线程池,原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法中断线程,无法响应中断的任务可能永远无法终止。
区别是 shutdownNow 首先将线程池的状态设为 STOP,然后尝试停止正在执行或暂停任务的线程,并返回等待执行任务的列表。而 shutdown 只是将线程池的状态设为 SHUTDOWN,然后中断没有正在执行任务的线程。

通常调用 shutdown 来关闭线程池,如果任务不一定要执行完可调用 shutdownNow 。

线程池是如何实现的

所谓的线程池中的“线程”,其实是被抽象为了一个静态内部类 Worker,它基于 AQS 实现,存放在线程池的HashSet workers 成员变量中。

而需要执行的任务则存放在成员变量 workQueue(BlockingQueue workQueue)中。这样,整个线程池实现的基本思想就是:从 workQueue 中不断取出需要执行的任务,放在 Workers 中进行处理。

线程池的好处

  • 降低资源消耗,复用已创建的线程,降低开销、控制最大并发数。
  • 隔离线程环境,可以配置独立线程池,将较慢的线程与较快的隔离开,避免相互影响。
  • 实现任务线程队列缓冲策略和拒绝机制。
  • 实现某些与时间相关的功能,如定时执行、周期执行等。

处理线程池的异常

处理线程池中的异常

1.用try-catch块来不住异常,打印错误信息

2.用callable接口实现任务,结果返回给future对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值