【java并发工具类-分工】如何正确创建线程池

1. 线程池消耗

Java创建对象,在堆内存中分配一块内存空间即可。而创建线程,需要调用操作系统内核的API,然后操作系统要为线程分配一系列资源,成本很高!

**线程是一个重量级对象,应避免频繁地创建销毁。**所以线程池方案很好。

2. 线程池其实是一种生产者消费者模式

线程池,你第一时间想到的是池化资源,就是需要使用的时候申请资源,用完释放资源,但是线程池不是这样的,也不能这样。

线程池其实是一种生产者-消费者模式,线程的使用方是生产者(生产任务task),线程池本身是消费者(消费任务task)。

2.1手动实现线程池

原理:线程池中添加一个阻塞队列,其中存放要执行的任务Runable,线程池构造方法时创建规定数量的Thread线程放入线程池列表中,并且start启动,内部run()函数while死循环不断从阻塞队列中取出任务执行。

//简化的线程池,仅用来说明工作原理
class MyThreadPool{
  //阻塞队列存放Runable任务
  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");});

3. 如何使用Java的线程池?

Java并发包中提供的线程池比我们上面实现的线程池好的多,核心是ThreadPoolExecutor

3.1 ThreadPoolExecutor构造方法

下面是ThreadPoolExecutor的构造函数。

ThreadPoolExecutor(
  int corePoolSize,
  int maximumPoolSize,
  long keepAliveTime,
  TimeUnit unit,
  BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory,
  RejectedExecutionHandler handler) 

下面来一一介绍线程池的参数:

  • corePoolSize:线程池中最少保有的最小线程数,即使程序没有需要执行的任务,也需要存在几个线程等待执行,如果当前线程存活较多,相应地减少存活线程数。

    • allowCoreThreadTimeOut(boolean value) 方法:项目很闲时,可以把线程都撤走,不留最小线程maximumPoolSize。
  • maximumPoolSize:表示线程池能创建的最大线程数,如果当前存活线程较少,相应增加线程数。

  • keepAliveTime & unit:上面两个参数根据线程在一段时间的执行情况,相应地增加减少线程,keepAliveTime & unit就是来定义这个时间的,如果一个线程空闲了keepAliveTime & unit这么久,而且线程数>corePoolSize,这个空闲的线程就会被收回。

  • workQueue:工作队列,和上面手动实现线程池的队列同义,存放要执行的任务。

  • threadFactory:可以根据这个自定义如何创建线程,例如给线程指定一个有意义的名字。

  • handler:可以通过这个自定义任务的拒绝策略,当线程池中所有线程都在执行,并且工作队列中的任务也满了,此时在有任务提交时,线程池就可以拒绝接受任务,而拒绝的策略,可以用handler来指定。
    ThreadPoolExecutor提供的四种策略:

    • CallerRunsPolicy:提交任务的线程自己去执行该任务。
    • AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
    • DiscardPolicy:直接丢弃任务,没有任何异常抛出。
    • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

3.2 线程池的使用注意事项

  1. 开发中尽量使用ThreadPoolExecutor,不要使用Executors快速创建线程池。
  2. 强烈建议使用有界队列。
  3. 默认拒绝策略,抛出异常,慎重使用,
  4. 异常处理:如ThreadPoolExecutor对象的executor方法,提交任务,如果任务执行期间发生异常,会导致线程终止,从而任务异常了,但是接收不到任何异常通知,让你误以为所有任务都正常执行。所以最稳妥的方法还是捕获异常。
try {
  //业务逻辑
} catch (RuntimeException x) {
  //按需处理
} catch (Throwable x) {
  //按需处理
} 

更多:邓新
参考:极客时间

发布了34 篇原创文章 · 获赞 0 · 访问量 1089

猜你喜欢

转载自blog.csdn.net/qq_42634696/article/details/105448220