java并发专题之线程池深刻浅出

1.线程池是什么

线程池(Thread Pool)是一种基于池化思想管理线程的工具,常常出如今多线程服务器中,如MySQL。java

咱们都知道线程的建立和销毁都须要必定的资源开销,下降了计算机的总体性能。那么有没有一种办法能避免频繁的线程建立和销毁呢?基于此就引出了线程池的概念,使用线程池能够带来一系列好处:缓存

  • 下降资源消耗:经过池化技术重复利用已建立的线程,下降线程建立和销毁形成的损耗。
  • 提升响应速度:任务到达时,无需等待线程建立便可当即执行。
  • 提升线程的可管理性:线程是稀缺资源,若是无限制建立,不只会消耗系统资源,还会由于线程的不合理分布致使资源调度失衡,下降系统的稳定性。使用线程池能够进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具有可拓展性,容许开发人员向其中增长更多的功能。好比延时定时线程池ScheduledThreadPoolExecutor,就容许任务延期执行或按期执行。

2.线程池的设计

Java中JDK8的线程池核心实现类是ThreadPoolExecutor,咱们首先来看一下ThreadPoolExecutor的UML类图,了解下ThreadPoolExecutor的继承关系。服务器

image-20210807111906325.png

ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何建立线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。ExecutorService接口增长了一些能力:(1)扩充执行任务的能力,补充能够为一个或一批异步任务生成Future的方法;(2)提供了管控线程池的方法,好比中止线程池的运行。AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法便可。最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另外一方面同时管理线程和任务,使二者良好的结合从而执行并行任务。markdown

3.线程池的实现

3.1. 经过Executors提供四种线程池:

一、newCachedThreadPool建立一个可缓存线程池,若是线程池长度超过处理须要,可灵活回收线程,若无可回收,则新建线程;线程池无限大,当执行第二个任务时第一个任务已完成,会复用执行第一个任务的线程,而不是新建线程。多线程

二、newFixedThreadPool建立一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;初始线程数和最大线程数同样,若是要执行的线程大于初始线程数,则会将多余的线程任务加入到缓存队列中等待执行。并发

三、newScheduledThreadPool建立一个定长线程池,支持定时及周期性任务的执行;框架

四、newSingleThreadExecutor建立一个单线程化的线程池,它只会用惟一的工做线程来执行任务,保证全部任务按照指定顺序(FIFO,LIFO,优先级)执行;异步

经过Executors建立的线程池有个致命缺点,以newCachedThreadPool来讲
public class ThreadPoolDemo {
    public static void main(String[] args)throws Exception {
        ExecutorService threadPool = Executors.newCachedThreadPool();
    }
}
复制代码

咱们点进newCachedThreadPool()方法会看到以下内容:高并发

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
//咱们发现第二个参数为Integer.MAX_VALUE,也就是最大的工做线程数为Integer.MAX_VALUE,若是超高并发过来会直接OOM。
复制代码

因此咱们得经过new ThreadPoolExecutor()来本身指定参数。工具

3.2经过ThreadPoolExecutor来建立线程池

构造方法以下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 复制代码
  • corePoolSize:表示核心线程的数量,就是线程池中最好要保证的线程数量。
  • maximumPoolSize:最大线程数量,就是当任务不少时,能工做的最大线程数。
  • keepAliveTime:线程的空闲时间。就是当实际工做的线程数小于最大线程数的时候,就有部分线程是处于空闲的状态,当这些空闲线程的空闲时间到达keepAliveTime就会被干掉。可是要保证最少线程数为corePoolSize。
  • unit:空闲时间都单位。
  • workQueue:工做队列,就是当工做线程数达到corePoolSize以后的任务会放入到工做队列中。
  • threadFactory:线程工厂,用于建立线程。
  • handler:拒绝处理器,就是当工做线程到达最大工做线程而且工做队列已经满了状况下,线程池应该怎么作。

3.3线程池的工做流程。

流程图以下:

image-20210807114929296.png

当任务过来时,首先会先去判断线程池中工做的线程数量是否到达核心线程数,若是没达到就直接执行,若是达到了就查看工做队列是否满。若是没满就将任务放入到工做队列中,若是满了就增长工做线程数来处理任务。若是工做线程和队列都满了的话就会用制定的策略去拒绝任务。

3.4 拒绝策略

  • AbortPolicy(默认):直接抛出异常RejectedExecutionException异常阻止系统正常运行。

  • CallerRunsPolicy:调用者运行是一种调节机制,该策略既不会抛弃任务,也不会抛弃异常,而是将某些任务回退到调用者,从而下降新任务的流量。

  • DiscardOldestPolicy:丢弃队列中等待最久的任务,而后把当前任务加入队列中尝试再次提交当前任务。

  • DiscardPolicy:直接丢弃任务,不予处理也不抛出异常。若是容许任务丢失,这是最好的一种方案。

代码演示拒绝策略:

1.AbortPolicy

public static void main(String[] args)throws Exception {
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    try {
        for (int i = 0; i < 10; i++) {
            poolExecutor.execute(()->{
                System.out.println(Thread.currentThread().getName()+"\t办理业务");
            });
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        poolExecutor.shutdown();
    }
    System.out.println(Thread.currentThread().getName()+"\t办理业务");
}
复制代码

image-20210807161417484.png

也就是说明当工做线程和工做队列都满了以后线程池会拒绝任务直接报错。

2.CallerRunsPolicy

public static void main(String[] args)throws Exception {
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());
    try {
        for (int i = 0; i < 10; i++) {
            poolExecutor.execute(()->{
                System.out.println(Thread.currentThread().getName()+"\t办理业务");
            });
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        poolExecutor.shutdown();
    }
    System.out.println(Thread.currentThread().getName()+"\t办理业务");
}
复制代码

image-20210807161606775.png

也就是说明当工做线程和工做队列都满了以后会将任务返还给调用线程池的人,让他去处理。

3.DiscardOldestPolicy和DiscardPolicy

public static void main(String[] args)throws Exception {
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.DiscardOldestPolicy());
    try {
        for (int i = 0; i < 10; i++) {
            poolExecutor.execute(()->{
                System.out.println(Thread.currentThread().getName()+"\t办理业务");
            });
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        poolExecutor.shutdown();
    }
    System.out.println(Thread.currentThread().getName()+"\t办理业务");
}
复制代码

image-20210807161728902.png

一共输出了10条记录,说明有一条消息被抛弃了。

相关文章
相关标签/搜索