Executor与线程池:你真的用好线程池了吗?

本文探讨了Java线程池的原理,特别是ThreadPoolExecutor的使用,介绍了如何避免频繁创建线程、定制化拒绝策略以及正确处理线程执行异常。重点讲解了线程池的工作机制和如何选择合适的构造参数,以及在高并发场景下的注意事项。
摘要由CSDN通过智能技术生成

引言

与创建对象不同,仅仅是在 JVM 的堆里分配一块内存而已;而创建一个线程,却需要调用操作系统内核的 API,然后操作系统要为线程分配一系列的资源,这个成本就很高了,所以线程是一个重量级的对象,应该避免频繁创建和销毁。那么就需要线程池来进行统一管理。

线程池是一种生产者 - 消费者模式

目前业界线程池的设计,普遍采用的都是生产者 - 消费者模式。线程池的使用方是生产者,线程池本身是消费者。下面的示例代码中,我们创建了一个非常简单的线程池MyThreadPool,你可以通过它来理解线程池的工作原理。

// 简化的线程池,仅用来说明工作原理
class MyThreadPool{
  // 利用阻塞队列实现生产者 - 消费者模式
  BlockingQueue<Runnable> workQueue;
  // 保存内部工作线程
  List<WorkerThread> threads = new ArrayList<>();
  // 构造方法
  MyThreadPool(int poolSize,BlockingQueue<Runnable> workQueue){
    this.workQueue = workQueue;
    // 创建工作线程
    for(int idx=0; idx<poolSize; idx++){
      WorkerThread work = new WorkerThread();
      work.start();
      threads.add(work);
    }
  }
  // 提交任务
  void execute(Runnable command){
    workQueue.put(command);
  }
  // 工作线程负责消费任务,并执行任务
  class WorkerThread extends Thread{
    public void run() {
    // 循环取任务并执行
    while(true){Runnable task = workQueue.take();
      task.run();
    }
   }
 }
}
/** 下面是使用示例 **/
// 创建有界阻塞队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2);
// 创建线程池
MyThreadPool pool = new MyThreadPool(10, workQueue);
// 提交任务
pool.execute(()->{
   System.out.println("hello");
});

在 MyThreadPool 的内部,我们维护了一个阻塞队列 workQueue 和一组工作线程,工作线程的个数由构造函数中的 poolSize 来指定。用户通过调用 execute() 方法来提交Runnable 任务,execute() 方法的内部实现仅仅是将任务加入到 workQueue 中。MyThreadPool 内部维护的工作线程会消费 workQueue 中的任务并执行任务,相关的代码就是代码①处的 while 循环。线程池主要的工作原理就这些。

如何使用 Java 中的线程池

Java提供的线程池相关的工具类中,最核心的是ThreadPoolExecutor,ThreadPoolExecutor 的构造函数非常复杂,如下面代码所示,这个最完备的构造函数有 7个参数。

ThreadPoolExecutor(
  int corePoolSize,
  int maximumPoolSize,
  long keepAliveTime,
  TimeUnit unit,
  BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory,
  RejectedExecutionHandler handler)
  • corePoolSize:表示线程池保有的最小线程数。
  • maximumPoolSize:表示线程池创建的最大线程数。
  • keepAliveTime & unit:如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。
  • workQueue:工作队列。
  • threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一
    个有意义的名字。
  • handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙
    碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就
    会拒绝接收。
    CallerRunsPolicy:提交任务的线程自己去执行该任务。
    AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
    DiscardPolicy:直接丢弃任务,没有任何异常抛出。
    DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,
    然后把新任务加入到工作队列。

使用线程池要注意些什么

  • 避免使用 Executors 你可以快速创建线程池:Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导
    致所有请求都无法处理,这是致命问题
  • 建议定制化业务的拒绝策略:线程池默认的拒绝策略会throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制catch 它,所以开发人员很容易忽略。因此默认拒绝策略要慎重使用。
  • 线程执行异常的处理:通过 ThreadPoolExecutor 对象的 execute()方法提交任务时,如果任务在执行的过程中出现运行时异常,会导致执行任务的线程终止;不过,最致命的是任务虽然异常了,但是你却获取不到任何通知,这会让你误以为任务都执行得很正常。虽然线程池提供了很多用于异常处理的方法,但是最稳妥和简单的方案还是try catch捕获所有异常并按需处理。

总结:
觉得有用的客官可以点赞、关注下!感谢支持🙏谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值