线程池的问题

一、什么是线程池?

  一个线程池管理一组工作的线程,同时它还包括一个用于放置等待执行任务的任务队列(阻塞队列)。

二、线程池的工作原理

  默认情况下,在创建线程池后,线程池中的线程数为0,当任务提交给线程池后处理策略如下:

  1. 如果线程池中的数量小于 corePoolSize(核心线程池大小),即使线程池中的线程都处于空闲状态,也要创建一个新的线程来处理被添加的任务。

  2. 如果线程池中的数量大于等于corePoolSize,但是缓冲队列 WorkQueue 未满,那么任务被放入到缓冲队列,则该任务会等待空闲线程将其去出去执行。

  3. 如果线程池中的数量大于等于corePoolSize,而且缓冲队列 WorkQueue 已满,并且线程池中的数量小于最大线程数 MaximumPoolSize,则建新的线程来处理被添加的任务。

  4. 如果线程池中的数量大于等于corePoolSize,而且缓冲队列 WorkQueue 已满,并且线程池中的数量等于最大线程数 MaximumPoolSize,则通过RejectedExceptionHandler所指定的策略(任务拒绝策略)来处理此任务。

  即处理任务的优先级为:corePoolSize -> WorkQueue -> MaximumPoolSize,如果3这都满,则交给 RejectedExceptionHandler 处理被拒绝的任务。

  5. 特别注意:在 corePoolSize 和 MaximunPoolSize 之间的线程会被自动释放。当线程数量大于corePoolSize时,如果某个线程空间超过KeepAliveTime,线程将被终止,直至线程池中线程数量不大于corePoolSize。这样,线程池可以动态调节池中线程的数量。

三、使用线程池的好处

  1. 通过重复利用已创建的线程,减少在创建和销毁线程上所花的时间以及系统资源的开销。

  2. 提高相应速度。当任务到达时,任务可以不需要等到线程创建就可以执行。

  3. 提高线程的可管理性。使用线程池可以对线程统一的分配和监控。

  4. 如果不使用线程池,系统会创建大量的线程导致消耗完系统内存。

四、线程池的创建

1. 接口与抽象类

  Executors 类(推荐)

  Executor 接口

  ExecutorService 接口

  AbstractExecutorService 抽象类

  ThreadPoolExecutor 类(不推荐)

 继承关系:Executor <- ExecutorService <- AbstractExecutorService <- ThreadPoolExecutor

2. Executors 类:(推荐使用)
  它主要用来创建线程池。
  Executors.newSingleThreadExecutor(); //创建容量为1 的线程池
  Executors.newFixedThreadPool(int n); //创建固定容量大小的线程池
  Executors.newCachedThreadPool(); //创建一个线程池,线程池最大容量为 Integer.MAX_VALUE(无界线程池)

 1 public static void testExecutor() {
 2         //ExecutorService service = Executors.newSingleThreadExecutor();
 3         ExecutorService service = Executors.newFixedThreadPool(2);//创建两个线程
 4         service.submit(new Runnable() {
 5             @Override
 6             public void run() {
 7                 for (int i = 0; i < 10; ++i) {
 8                     try {
 9                         Thread.sleep(1000);
10                         System.out.println("Executor1:" + i);
11                     } catch (Exception e) {
12                         e.printStackTrace();
13                     }
14                 }
15             }
16         });
17 
18         service.submit(new Runnable() {
19             @Override
20             public void run() {
21                 for (int i = 0; i < 10; ++i) {
22                     try {
23                         Thread.sleep(1000);
24                         System.out.println("Executor2:" + i);
25                     } catch (Exception e) {
26                         e.printStackTrace();
27                     }
28                 }
29             }
30         });
31 
32         service.shutdown();
33         while (!service.isTerminated()) {
34             try {
35                 Thread.sleep(1000);
36                 System.out.println("Wait for termination.");
37             } catch (Exception e) {
38                 e.printStackTrace();
39             }
40         }
41     }

3. ThreadPoolExecutor 类

1 public ThreadPoolExecutor(int corePoolSize,
2                               int maximumPoolSize,
3                               long keepAliveTime,
4                               TimeUnit unit,
5                               BlockingQueue<Runnable> workQueue,
6                               RejectedExecutionHandler handler);

  corePoolSize:核心线程数。

  maximunPoolSize:线程池最大线程数

  keepAliveTime:默认情况下,线程池线程数大于 corePoolSize 后,keepAliveTime 才起作用,如果一个线程空闲时间达到 keepAliveTIme,则会终止,直到线程数不超过 corePoolSize

  unit:参数keepAliveTime的 的时间参数

  workQueue:阻塞队列,任务缓存队列,存放等待执行的任务

    workQueue 的类型为BlockingQueue接口 ,有三种实现类型:

    1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

    2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

    3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。在某次添加元素后必须等待其他线程取走后才能继续添加

  ThreadFactor:线程工厂,用来创建线程

  handler:表示当拒绝任务时的策略,有以下取值:

    1.AbortPolicy:直接抛出异常(默认的)

    2.DiscardPolicy:直接丢弃任务

    3.DiscardOldestPolicy:丢弃队列中最旧(队头)的任务,并执行当前任务

    4.CallerRunsPolicy:不用线程池中的线程执行,用调用者所在线程执行

ThreadPoolExecutor 有以下几个非常重要的方法:

  execute()

  submit()

  shutdown()

  shutdownNow()

execute() 与 submit() 的区别:

  1. submit 有返回值,execute 没有返回值。所以说可以根据任务有无返回值选择对应的方法。  

  2.  submit 方便异常的处理。如果任务可能会抛出异常,而且希望外面的调用者能够感知这些异常,那么就需要调用submit 方法,通过捕获Future.get抛出的异常。

shutdown() 与 shutdownNow() 的区别:

  1.都是用来关闭线程池的。

  2. shutdown 方法:此方法执行后不得向线程池再提交任务,如果有空闲线程则销毁空闲线程,等待所有正在执行的任务及位于阻塞队列中的任务执行结束,然后销毁所有线程。

  3. shutdownNow 方法:此方法执行后不得向线程池再提交任务,如果有空闲线程则销毁空闲线程,取消所有位于阻塞队列中的任务,并将其放入List<Runnable>容器,作为返回值。取消正在执行的线程(实际上仅仅是设置正在执行线程的中断标志位,调用线程的interrupt 方法来中断线程)。

原文地址:https://www.cnblogs.com/yumingxing/p/9555919.html