前言

前两天趁着假期在整理粉丝私信的时候看到一个粉丝朋友的私信跟我说自己现在正在复习准备面试,自己在复习到线程池这一块的时候有点卡壳,总感觉自己差了点什么。想要我帮他指导一下。这不趁着假期我也有时间我把自己这么多年的理解和从网上找的资料放在一块整理了一下都放在下面了!

1.什么是线程池

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,我们的程序最终都是由线程进行运作。在Java中,创建和销毁线程的动作是很消耗资源的,因此就出现了所谓“池化资源”技术。线程池是池化资源技术的一个应用,所谓线程池,顾名思义就是预先按某个规定创建若干个可执行线程放入一个容器中(线程池),需要使用的时候从线程池中去取,用完之后不销毁而是放回去,从而减少了线程创建和销毁的次数,达到节约资源的目的。

2.为什么要使用线程池

2.1 降低资源消耗

前面已经讲到线程池的出现减少了线程创建和销毁的次数,每个线程都可以被重复利用,可执行多个任务。

2.2 提高系统的响应速度

每当有任务到来时,直接复用线程池中的线程,而不需要等待新线程的创建,这个动作可以带来响应速度的提升

2.3 防止过多的线程搞坏系统

可以根据系统的承受能力,调整线程池中的工作线程的数量,防止因为线程过多服务器变慢或死机。java一个线程默认占用空间为1M,可以想象一旦手动创建线程过多极有可能导致内存溢出。

3.线程池主要参数

我们可以用Executors类来创建一些常用的线程池,但是像阿里是禁止直接通过Executors类直接创建线程池的,具体的原因稍后再谈。

在了解Executors类所提供的几个线程池前,我们首先来了解一下ThreadPoolExecutor的主要参数,ThreadPoolExecutor是创建线程池的类,我们选取参数最多的构造方法来看一下:

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

  

 

3.1 corePoolSize

当向线程池提交一个任务时,如果线程池中已创建的线程数量小于corePoolSIze,即便存在空闲线程,也会创建一个新线程来执行任务,直到创建的线程数大于或等于corePoolSIze。

3.2 maximumPoolSize

线程池所允许的最大线程个数,当队列满了且已经创建的线程数小于maximumPoolSize时,会创建新的线程执行任务。

3.3 keepAliveTime

当线程中的线程数大于corePoolSIze时,如果线程空闲时间大于keepAliveTime,该线程就会被销毁。

3.4 unit

keepAliveTime的时间单位

3.5 workQueue

用于保存等待执行任务的队列

3.6 threadFactory

用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n

3.7 handler

拒绝策略,当线程池和队列满了之后,再加入新线程后会执行此策略。

下面是四种线程池的拒绝策略:

AbortPolicy:中断任务并抛出异常

DiscardPolicy:中段任务但是不抛出异常

DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试提交新任务

CallerRunsPolicy:由调用线程处理该任务

4.线程池执行流程

当我们了解了ThreadPoolExecutor的七个参数后,我们就可以很快的理解线程池的流程:

 

当提交任务后,首先判断当前线程数是否超过核心线程数,如果没超过则创建新线程执行任务,否则判断工作队列是否已满,如果未满则将任务添加到队列中,否则判断线程数是否超过最大线程数,如果未超过则创建线程执行任务,否则执行拒绝策略。

5.Executors提供的线程池

executors提供了许多种线程池供用户使用,虽然很多公司禁止使用executors创建线程池,但是对于刚开始解除线程池的人来说,Executors类所提供的线程池能很好的带你进入多线程的世界。

5.1 newSingleThreadExecutor

ExecutorService executorService = Executors.newSingleThreadExecutor();

  

听名字就可以知道这是一个单线程的线程池,在这个线程池中只有一个线程在工作,相当于单线程执行所有任务,此线程可以保证所有任务的执行顺序按照提交顺序执行,看构造方法也可以看出,corePoolSize和maximumPoolSize都是1。

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}

  

5.2 newFixedThreadPool

ExecutorService executorService = Executors.newFixedThreadPool(2);

  

固定长度的线程池,线程池的长度在创建时通过变量传入。下面是newFixedThreadPool的构造方法,corePoolSize和maximumPoolSize都是传入的参数值

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}

  

5.3 newCachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();

  

可缓存线程池,这个线程池设定keepAliveTime为60秒,并且对最大线程数量几乎不做控制。

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

  

观察构造方法,corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制。设定keepAliveTime 为60秒,线程空闲60秒后自动结束,因为该线程池创建无限制,不会有队列等待,所以使用SynchronousQueue同步队列。

5.4 newScheduledThreadPool

创建一个定时的线程池。此线程池支持定时以及周期性执行任务的需求。下面是newScheduledThreadPool的用法:

Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"thread1");
}
});
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"thread2");
}
});
Thread thread3=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"thread3");
}
});
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
//在1000ms后执行thread1
scheduledExecutorService.schedule(thread1,1000,TimeUnit.MILLISECONDS);
//在1000ms后每隔1000ms执行一次thread2,如果任务执行时间比间隔时间长,则延迟执行
scheduledExecutorService.scheduleAtFixedRate(thread2,1000,1000,TimeUnit.MILLISECONDS);
//和第二种方式类似,但下一次任务开始的时间为:上一次任务结束时间(而不是开始时间) + delay时间
scheduledExecutorService.scheduleWithFixedDelay(thread3,1000,1000,TimeUnit.MILLISECONDS);

  

6.为什么阿里巴巴禁止程序员用Exectors创建线程池

如果你的idea装了Alibaba Java Codeing Guidelines插件(推荐大家使用,有助于让你的代码更加规范),那么当你写了Exectors创建线程池后会看到提示:

 

并且阿里将这个用法定义为Blocker,即不允许使用,而是让人们用ThreadPoolExecutor的方式创建线程池。原因是通过ThreadPoolExecutor的方式,这样的处理方式让写的人更加明确线程池的运行规则,规避资源耗尽的风险。

 

Executors返回的线程池对象的弊端如下:

1)FixedThreadPool和SingleThreadPool:

允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

2)CachedThreadPool:

允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

下面是ThreadPoolExecutor创建线程池的简单例子

int corePoolSize=5;
int maximumPoolSize=10;
long keepAliveTime=30;
BlockingQueue blockingQueue=new ArrayBlockingQueue(2);
RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, blockingQueue, handler);
threadPoolExecutor.execute(thread1);

  

7.总结

那么今天这篇关于线程池的文章就到这里了,大家看完有什么不懂的欢迎在下方留言讨论,有什么不懂的也可以私信问我。另外在这里说一下私信回复的问题,因为我平时跟大家一样也要上班,私信一般是晚上会抽空看一下,一般看到大家问的问题我都会帮忙回复解决,有时间忙的时候也可能几天没时间看,这点希望大家可以见谅。

最后

欢迎关注公众号:【前程有光】,领取一套227页的2020最新的Java面试宝典

新鲜出炉!JAVA线程池精华篇深度讲解,看完你还怕面试被问到吗?的更多相关文章

  1. java 线程池第一篇 之 ThreadPoolExcutor

    一:什么是线程池? java 线程池是将大量的线程集中管理的类,包括对线程的创建,资源的管理,线程生命周期的管理.当系统中存在大量的异步任务的时候就考虑使用java线程池管理所有的线程.减少系统资源的 ...

  2. 跟着阿里p7一起学java高并发 - 第18天:玩转java线程池,这一篇就够了

    java中的线程池,这一篇就够了 java高并发系列第18篇文章. 本文主要内容 什么是线程池 线程池实现原理 线程池中常见的各种队列 自定义线程创建的工厂 常见的饱和策略 自定义饱和策略 线程池中两 ...

  3. 深入浅出Java线程池:源码篇

    前言 在上一篇文章深入浅出Java线程池:理论篇中,已经介绍了什么是线程池以及基本的使用.(本来写作的思路是使用篇,但经网友建议后,感觉改为理论篇会更加合适).本文则深入线程池的源码,主要是介绍Thr ...

  4. 捕获Java线程池执行任务抛出的异常

    捕获Java线程池执行任务抛出的异常Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常, public interface Runnable { publi ...

  5. Java线程池详解,看这篇就够了!

    构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段. 基础知识 Execu ...

  6. Java线程池的了解使用—筑基篇

    前言 Java中的线程池是一个很重要的概念,它的应用场景十分广泛,可以被广泛的用于高并发的处理场景.J.U.C提供的线程池:ThreadPoolExecutor类,可以帮助我们管理线程并方便地并行执行 ...

  7. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

  8. Java线程池的原理及几类线程池的介绍

    刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...

  9. Java 线程池框架核心代码分析

    前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和资源消耗都是很高的.线程池应运而生,成为我们管理线程的利器.Java 通过Executor接口,提供了一种标准的方法将任务的提交过 ...

随机推荐

  1. 编译安装tree命令

    查看当前的tree [12:33:33 root@C8[ ~]#rpm -qi tree Name : tree Version : 1.7.0 Release : 15.el8 Architectu ...

  2. 小C和小派的缠绵爱情——C语言调用Python代码

    我妒忌你的开源,你眼红我的速度,不如我们就在一起吧! --------SJ2050 2019.4.9号更新:实现在未安装python环境的机子上运行调用了python程序的C语言代码! 文章目录 环境 ...

  3. pytest参数化代码笔记

    #!/usr/local/bin/python3 # -*- coding: utf-8 -*- import pytest __author__ = "Carp-Li" __da ...

  4. Nacos配置中心使用

    在系统开发过程中,开发者通常会将一些需要变更的参数.变量等从代码中分离出来独立管理,以独立的配置文件的形式存在.目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进 ...

  5. git学习(七) git的标签

    git的标签操作 git标签操作 git tag 不加任何参数 表示显示标签(按字母序) 非按时间 git tag 标签名 默认是给最近一次提交打上标签 git tag 标签名 commitId 给响 ...

  6. oracle基本学习

    oracle目录及卸载 1.oracle的目录介绍: oradata:数据库存储文件的目录 db_home: network >admin:配置网络服务和监听器服务 jdk:oracle自带jd ...

  7. Spring 5的最后一个特性版本5.3发布,4.3将于12月终止维护

    10月27日,Spring Framework团队宣布了5.3版本正式GA,Spring用户可以在repo.spring.io和Maven Central上获取到最新版本的依赖包. JDK的版本支持 ...

  8. TCP/IP 基础知识

    我把自己以往的文章汇总成为了 Github ,欢迎各位大佬 star https://github.com/crisxuan/bestJavaer 已提交此篇文章 要说我们接触计算机网络最多的协议,那 ...

  9. Java导出Pdf格式表单

    前言   作为开发人员,工作中难免会遇到复杂表单的导出,接下来介绍一种通过Java利用模板便捷导出Pdf表单的方式 模拟需求   需求:按照下面格式导出pdf格式的学生成绩单 准备工作 Excel软件 ...

  10. 辨析:IIR(Infinite Impulse Response)与FIR(Finite Impulse Response)

    IIR和FIR系统描述的是系统的抽样响应特性,其中$ x(n)=\delta(n) $ 举例:一个平均器的系统是FIR系统,因为它的抽样响应仅在变量n取某3个值的时候有值,是有限长的:一阶自回归模型由 ...