zoukankan      html  css  js  c++  java
  • 多线程编程核心技术(十七)线程池

      创建一个线程只需要,New Thread();,就可以完成,但是在JVM里面是一个很重的操作,需要调用操作系统内核的 API,然后操作系统要为线程分配一系列的资源。

      所以线程是一个重量级的对象,应该避免频繁创建和销毁。

      线程池是一种生产者-消费者模式,目前对于池化技术的实现一般都需要依赖容器来进行实现。下面是一个简单的伪代码,逻辑其实就是依赖一个队列,然后让队列中的线程循环执行传入的任务。

    //简化的线程池,仅用来说明工作原理
    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");
    });

      Java 并发包里提供的线程池比上面的复杂的多,最核心的是 ThreadPoolExecutor,ThreadPoolExecutor 的构造函数非常复杂,如下面代码所示,这个最完备的构造函数有 7 个参数。

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

      你可以把线程池类比为一个项目组,而线程就是项目组的成员。

    • corePoolSize:表示线程池保有的最小线程数。有些项目很闲,但是也不能把人都撤了,至少要留 corePoolSize 个人坚守阵地。
    • maximumPoolSize:表示线程池创建的最大线程数。当项目很忙时,就需要加人,但是也不能无限制地加,最多就加到 maximumPoolSize 个人。当项目闲下来时,就要撤人了,最多能撤到 corePoolSize 个人。
    • keepAliveTime 和 unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?很简单,一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。
    • workQueue:工作队列,和上面示例代码的工作队列同义。
    • threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
    • handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。ThreadPoolExecutor 已经提供了以下 4 种策略。
      • CallerRunsPolicy:提交任务的线程自己去执行该任务。
      • AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
      • DiscardPolicy:直接丢弃任务,没有任何异常抛出。
      • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

      Java 在 1.6 版本还增加了 allowCoreThreadTimeOut(boolean value) 方法,它可以让所有线程都支持超时,这意味着如果项目很闲,就会将项目组的成员都撤走。这边要注意的是如果是自己实现一个线程池最好还是使用有界队列,因为无界队列很容易出现OOM,这也是无建议使用静态的Executors的原因。使用有界队列,当任务过多时,线程池会触发执行拒绝策略,线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 它,所以开发人员很容易忽略。因此默认拒绝策略要慎重使用。如果线程池处理的任务非常重要,建议自定义自己的拒绝策略;并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用

      submit提交的任务,出现runtime异常时异常会被吞掉。setUncaughtExceptionHandler也只能捕获execute提交的task抛出的异常。submit提交时,通过返回的Future的get捕获异常。所以最好还是按需处理

    try {
      //业务逻辑
    } catch (RuntimeException x) {
      //按需处理
    } catch (Throwable x) {
      //按需处理
    } 
    

      

  • 相关阅读:
    HYSBZ 3813 奇数国
    HYSBZ 4419 发微博
    HYSBZ 1079 着色方案
    HYSBZ 3506 排序机械臂
    HYSBZ 3224 Tyvj 1728 普通平衡树
    Unity 3D,地形属性
    nginx 的naginx 种包含include关键字
    Redis 出现NOAUTH Authentication required解决方案
    mysql 8.0出现 Public Key Retrieval is not allowed
    修改jar包里的源码时候需要注意的问题
  • 原文地址:https://www.cnblogs.com/SmartCat994/p/14228745.html
Copyright © 2011-2022 走看看