线程池的理解与运用

前言:

对于线程池而言,也是在我们日常使用最为常用到的,今天就来剖析一下具体的细节,以下都是个人的拙见,理解有限,有什么不得体的地方还希望大家能不吝赐教。一起进步呀!!!

多线程的创建。

创建线程的方法:

  • 继承Thread类实现多线程。
    • 因为说 对于Thread的本质也是实现了Runnable接口的一个实例
  • 覆写实现 Runable 接口
    • 如果说 已经继承了一个类 就不能够继承Thread,就来实现runnable接口
  • 实现Callable 接口 有返回值的任务必须实现Callable接口
    无须返回值的对象的任务必须 Runnable 接口
    注意:这里也是一个需要注意的地方,一个是有返回值一个没有返回值,在面试中很有可能会问到两者之间的区别。
  • 使用到线程池: 我们都知道 使用 submit 和execute都可以添加线程任务 但是 submit有返回值 execute没有返回值。
  • 使用 ThreadPoolExecutor 方式。虽然在jdk 中提供了很多 的创建线程池的方法例如 newFixedThreadPool() ,newSingleThreadExecutor(),等 但是容易出现 OOM问题。

线程结束的三种方法
run或者是call方法执行完成以后
在运行的过程中 抛出一个未被捕获的异常 或者error
直接调用线程的stop方法来结束线程 导致死锁。

线程池的初步使用

线程池的使用

public class Pool {
    public static void main(String[] args) {
        List<String> strings= Arrays.asList("1","2");
      ExecutorService executorService= Executors.newFixedThreadPool(5);
      //一次多线程
       // ExecutorService executorService= Executors.newSingleThreadExecutor();  //一次一个线程
        ExecutorService executorService= Executors.newCachedThreadPool();’
        // 这个而言就是没有限制的信息。

        try{
            for (int i = 0; i < 10; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"\t 班里业务");
                });
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }
    }
}

以下就是底层的函数的实现里面传进去的参数。

  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }                                    

手写一个线程:

ExecutorService executorService=new ThreadPoolExecutor(
        2,
        5,
        1L,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(3),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());

线程池的底层:
线程池的理解与运用_第1张图片

线程池的主要流程:
线程池的理解与运用_第2张图片

在回答以上的问题的时候 先来完善线程池的处理过程

  1. 在创建完线程池以后,等待提交过来的任务请求
  2. 调用 execute()方法添加一个任务请求,线程池会做如下的判断。
    • 2.1 如果当前正在运行的线程数小于或等于corePoolSIze那么就会马上创建线程然后运行这个任务。
    • 2.2 如果当前正在运行的线程数大于 corePoolSIze,那么会将这个任务放入到队列中。
    • 2.3 如果此时队列满了,并且正在运行的线程数量还小于maximumPoolSIze ,此时就需要创建非核心线程运行这个任务。
    • 2.4 如果队列满了并且线程数大于或等于maximumPoolSize,那么会启动饱和策略来执行。
  3. 当一个线程完成时,会从队列中取下一个任务来执行。
  4. 当一个线程无事可做,且超过一定的时间(keepAliveTime)时,线程池会判断:
    如果当前运行的线程数大于corePoolSize,那么这个线程会停掉。
    所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

线程池的七大参数

  1. corePoolSize(线程池的基本大小): 线程池中的常驻核心线程数。
  2. maximumPoolSize 线程池中能够同时执行的最大线程数,此值必须大于等于1.
  3. keepaliveTime : 多于空闲线程的存活时间,若是线程池中的线程的个数超过 coolPoolSize时候。当空闲线程时间到达这个值时候,多于的空闲线程会被销毁,只剩下 corePoolSize个线程为止
  4. unit: keepAliveTime的时间单位。
  5. workQueue: 表示的是被提交但是尚未被执行的任务(阻塞队列,在队列中存在)。
  6. ThreadFactory:表示生成线程池中的工作线程的线程工厂,用于创建线程,一般为默认线程工厂即可
  7. handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝来请求的Runnable的策略
    对于线程池的七大参数是线程池中最为重要的部分,在面试时候经常会出现给你其中的几个参数让你构造一个线程池,并讲解其实现的状态。

在讲解完具体的线程池参数以后,上面讲到了在不能够继续处理线程以后的拒绝策略,以下介绍一下具体的拒绝策略。

线程池的四种拒绝策略

  1. AbortPolicy(默认):直接抛出 RejectedExecutionException 异常阻止系统的正常运行。
  2. CallerRunPolicy “调用者运行”一种调节机制,该策略既不会丢弃任务,也不会抛出异常,而是将某些任务会退给调用者,从而降低新任务的流量。
  3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
  4. DiscaPolicy直接丢弃任务,不予任何也不抛出异常。如果允许任务的失败这是最好的一种方案

底层:以上内置策略均实现了RejectExecutionHandler接口

何时使用: 等待队列已经排满了,再也塞不下新任务,同时线程池中线程也已经达到maximumPoolSize数量,无法继续为新任务服务,这个时候就需要使用拒绝策略来处理。

Execuors类实现的几种线程池类型,阿里为啥不让用?

常用的有四种线程池类型。

方法名 解释
newCachedThreadPool (ThreadPoolExecutor) 创建的是一个可缓存的线程池, 如果此时线程池的大小超过了处理人物所需要的线程,那么就会回收部分空闲(60秒不执行任务)的此案城,当任务数在增加的时候,这个线程池又可以只能的添加新县城来处理人物。此线程池不会对线程池的大小做限制,线程池大小完全依赖于操作系统(或者说是JVM)能够创建的最大线程大小
newFixedThreadPool (ThreadPoolExecutor) 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newSingleThreadExecutor(ThreadPoolExecutor) 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newScheduledThreadPool (ScheduledThreadPoolExecutor) 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

关于阿里为什么不让使用Executors去创建 线程池 。而是通过 ThreadPoolExecutor方式,是为了让我们明确线程池的运行规则,规避资源耗尽的风险:
1) newFixedThreadPool和newSingleThreadExecutor: 
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool: 
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

你可能感兴趣的:(多线程/高并发)