Java并发23 线程池的使用

线程池的使用

            • 补:上篇文章中线程池中的全局锁问题
            • 使用线程池的目的
            • 线程池ThreadPoolExecutor详细介绍
            • ThreadPoolExecutor简单使用

补:上篇文章中线程池中的全局锁问题

在上篇文章中,提到线程池在创建线程的时候需要获取全局锁,这里我们就看看源码,查看addWorker()方法

private boolean addWorker(Runnable firstTask, boolean core){
   	  // ...
      // 省掉上面部分代码
      boolean workerStarted = false;
      boolean workerAdded = false;
      Worker w = null;
      try {
          // 提交的任务封装成Worker
          w = new Worker(firstTask);
          final Thread t = w.thread;
          if (t != null) {
              // 这里使用可重入锁保证增加线程这一过程的原子性
              final ReentrantLock mainLock = this.mainLock;
              mainLock.lock();
              try {
                  //在获得锁期间再次检查线程池的运行状态:如果
                  //线程池已经关闭或者任务为空则抛出异常
                  int rs = runStateOf(ctl.get());
                  if (rs < SHUTDOWN ||
                      (rs == SHUTDOWN && firstTask == null)) {
                      if (t.isAlive()) 
                          throw new IllegalThreadStateException();
                      //加入Workers 
                      workers.add(w);
                      int s = workers.size();
                      if (s > largestPoolSize)
                          largestPoolSize = s;
                      workerAdded = true;
                  }
              } finally {
                  mainLock.unlock();
              }
              if (workerAdded) {
                  //如果添加成功则启动线程执行任务
                  t.start();
                  workerStarted = true;
              }
          }
      } finally {
          if (! workerStarted)
              addWorkerFailed(w);
      }
      return workerStarted;
  }

从上面的源码中我们看到线程池新建线程时使用了可重入锁ReentrantLock ,会影响线程池的并发效率,这里就引出了线程池中阻塞队列的作用:缓冲,将提交上来的任务先放进阻塞队列中,等到线程池中有可用的工作线程时,再去执行对应的任务,一方面缓冲由全局锁导致的并发效率问题,一方面让线程池中的线程达到复用的目的,减少系统资源开销。下面我们就来详细介绍下线程池

使用线程池的目的

众所周知,线程是稀缺资源,线程的创建和销毁都对系统资源造成很大的消耗,我们使用线程池,线程可以到达复用,从而降低资源消耗;当任务提交上来,无须等待线程创建就可以立即使用可用的线程去执行任务, 提高了任务的响应速度;使用线程池可以进行统一的分配、调优和监控线程

线程池ThreadPoolExecutor详细介绍
public ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,
                            ThreadFactory threadFactory,
                            RejectedExecutionHandler handler)
  1. corePoolSize:核心线程池大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建
  2. maximumPoolSize:线程池运行创建的最大线程数,如果队列已满,并且已创建的线程数小于maximumPoolSize时,则线程池会再创建新的线程执行任务,(如果使用了无界队列则这个参数就失效了
  3. keepAliveTime:线程保活时间,线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
  4. TimeUnit: 线程保活时间单位,使用了枚举类TimeUnit
  5. BlockingQueue:阻塞队列,用于保存等待执行的任务,有:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue 后面的文章讲单独详细介绍这些工作队列。
  6. ThreadFactory:用于设置创建线程的工厂,可以使用线程工厂给每个创建的线程设置更有实际生产意义的名字,方便出错时回溯。默认使用的是Executors.defaultThreadFactory(),当然我们也可以实现ThreadFactory接口去自定义,也可以使用开源框架guava提供的ThreadFactoryBuilder快速给线程池的线程设置有意义的名字。
  7. RejectedExecutionHandler:饱和策略,当工作队列和线程池都已满时,此时线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认为:AbortPolicy,可选有CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
ThreadPoolExecutor简单使用

上面介绍的线程池中各个组件,后面会逐一地结合上对应的例子详细介绍,这里我们先写一个简单的线程池例子。后面文章中的举例也基本在此例子上进行扩展。

public class ThreadPoolExecutorTest {

  private static ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new
          ArrayBlockingQueue<>(10), new ThreadPoolExecutor.AbortPolicy());

  public static void main(String[] args){
      for (int i = 1; i <= 14; i++) {
          Callable<Boolean> task = createTask(i);
          // 提交任务到线程池
          pool.submit(task);
          // 打印下线程池的基本信息
          System.out.println("after task:" + i + " submited, current active count: "
                  + pool.getActiveCount() + ", size of queue: " + pool.getQueue().size());
      }
      // 关闭线程池
      pool.shutdown();
  }

  /**
   * @Author Xu hao
   * @Description 创建任务
   * @Date 2019/3/19 0:08
   * @param i
   * @return java.util.concurrent.Callable
   **/
  private static Callable<Boolean> createTask(int i){

      Callable<Boolean> callable = () -> {
          TimeUnit.SECONDS.sleep(10);
          System.out.println("thread: " + Thread.currentThread().getName() + " execute task: " + i);
          return true;
      };

      return callable;
  }
}

执行结果的部分日志如下:

after task:1 submited, current active count: 1, size of queue: 0
after task:2 submited, current active count: 2, size of queue: 0  // 此时两个核心线程,去执行两个任务 刚刚好 工作队列闲置
after task:3 submited, current active count: 2, size of queue: 1 // 此时开始核心线程已满,任务开始放入工作队列中
after task:4 submited, current active count: 2, size of queue: 2
after task:5 submited, current active count: 2, size of queue: 3
after task:6 submited, current active count: 2, size of queue: 4
after task:7 submited, current active count: 2, size of queue: 5
after task:8 submited, current active count: 2, size of queue: 6
after task:9 submited, current active count: 2, size of queue: 7
after task:10 submited, current active count: 2, size of queue: 8
after task:11 submited, current active count: 2, size of queue: 9
after task:12 submited, current active count: 2, size of queue: 10
after task:13 submited, current active count: 3, size of queue: 10 // 此时,核心线程已满,工作队列也已满,创建新的线程执行任务
after task:14 submited, current active count: 4, size of queue: 10

你可能感兴趣的:(Java并发)