美文网首页JUC并发包源码实战
J.U.C并发包之Executor与线程池

J.U.C并发包之Executor与线程池

作者: 语落心生 | 来源:发表于2019-07-08 14:19 被阅读0次

java创建对象的过程中,创建对象,是在 JVM 的堆里分配一块内存;而创建一个线程,需要调用操作系统内核的 API,然后操作系统要为线程分配一系资源

池化资源与线程池的区别

  • 池化资源是指把资源加载到内存当中,通过创建一个池化类统一获取和释放资源
class XXXPool{
  // 获取池化资源
  XXX acquire() {
  }
  // 释放池化资源
  void release(XXX x){
  }
}  
  • 采用一般意义上池化资源的设计方法

class ThreadPool{
  // 获取空闲线程
  Thread acquire() {
  }
  // 释放线程
  void release(Thread t){
  }
} 
// 期望的使用
ThreadPool pool;
Thread T1=pool.acquire();
// 传入 Runnable 对象
T1.execute(()->{
  // 具体业务逻辑
  ......
});

假设我们获取到一个空闲线程T1,然后该如何使用T1呢?通过调用T1的execute方法,传入一个runnable对象来执行具体的业务逻辑,但实际上Thead对象中并没有execute这样的方法,所以只能放弃这种做法。采用之前在FutrueTask一文中提到的阻塞队列为主的生产者-消费者模式

线程池作为一个生产和释放线程的地方,必然是属于消费线程的一方,而线程的使用方则是生产者。实现一个简单的线程池如下

12.jpg
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");
});

图中的 product 是往内部队列里写消息的生产者,并不是往这个 Consumer 所在的线程池中写任务的生产者。因为即便 Consumer 是一个单线程的线程池,它依然具有一个常规线程池所具备的所有条件:

  • Worker 调度线程,也就是线程池运行的线程;虽然只有一个。
  • 内部的阻塞队列;虽然长度只有1。

没有生产者往里边丢任务是指consumer放大后的那一块,也就是内部队列并没有其他线程往里边丢任务执行 execute() 方法。
而一旦发生未捕获的异常后,Worker1 被回收,顺带的它所调度的线程 task1(这个task1 也就是在执行一个 while 循环消费左图中的那个队列) 也会被回收掉。
新创建的 Worker2 会取代 Worker1 继续执行 while 循环从内部队列里获取任务,但此时这个队列就一直会是空的,所以也就是处于 Waiting 状态。

所以在高负载条件下,当队列内的任务一多,就会发生内存溢出。建议使用有界队列,当任务过多时,线程池会采用拒绝策略。线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 。所以尽量使用自定义异常策略

此外,通过 ThreadPoolExecutor 对象的ThreadPoolExecutor的execute方法提交任务时,如果任务执行过程中抛出异常,会导致线程终止
故做如下处理:

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

相关文章

网友评论

    本文标题:J.U.C并发包之Executor与线程池

    本文链接:https://www.haomeiwen.com/subject/qprohctx.html