第二部分:并发工具类22->Executor与线程池,如何创建正确的线程池

1.创建线程

创建对象,仅仅在jvm堆里分配一块内存
创建线程,调用操作系统内核api,操作系统为线程分配一系列资源,线程是重量级对象,应该避免频繁创建和销毁

2.线程池

线程池和一版池化资源不同,一般池化资源是acquire申请资源,release释放资源
java提供的线程池里没有申请线程和释放线程的方法


class XXXPool{
  // 获取池化资源
  XXX acquire() {
  }
  // 释放池化资源
  void release(XXX x){
  }
}  

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

而不是普通池化资源的使用方式
线程池使用方式生产者
线程池本身是消费者

线程池设计思想
内嵌队列,以及工作线程


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

线程池中有多少个线程,就起多少个工作线程WorkerThread
execute只是把任务提交到queue中,线程池内部维护的工作线程会消费queue中的任务并执行

4.如何用java中的线程池


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

corePoolSize : 最小线程数,有些项目很闲,至少保留corePoolSize坚守阵地
maximumPoolSize:线程池最大线程数
keepAliveTime & unit : 一个线程在一段时间内,没有执行任务,说明很闲。keepaliveTime 和unit定义这个一段时间参数,如果一个线程空闲了keepaliveTime & unit 这么久 ,并且线程池的线程数量大于corePoolSize,那么空闲线程就要回收

workQueue:工作队列
threadFactory:定义如何创建线程,给线程指定有意义的名字
handler:定义任务的拒绝策略,如果线程池所有线程都在忙,工作队列也满了,那么提交任务,线程池就会拒绝接收
以下4种拒绝策略:
CallerRunsPolicy:提交任务的线程自己去执行该任务。
AbortPolicy:默认拒绝策略,会throws RejectedExecutionException。
DiscardPolicy:直接丢弃任务,没有任何异常抛出。
DiscardOldestPolicy:丢弃最老的任务,把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

5.使用线程池的注意事项

目前大厂都不建议直接使用静态工厂类Executors了
主要原因是Executors提供的很多方法默认使用的是*队列LinkedBlockingQueue,高负载情况,*队列很容易OOM
OOM会导致所有请求无法处理,致命问题,所以建议使用有界队列

使用有界队列,任务过多时,默认拒绝策略会throw RejectedExecutionException运行时异常,运行时异常编译器不强制catch它,开发人员很容易忽略,拒绝策略要谨慎使用。线程池处理的任务非常重要,建议自定义拒绝策略,自定义拒绝策略往往和降级策略配合使用

线程池里面提交任务,任务要自己捕获异常。

上一篇:JUCday08


下一篇:面试官:说一下线程池内部工作原理?