一篇文章带你完全了解JAVA线程池,再也不用担心被面试官问了

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

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.总结

关于本篇文章如果有任何错误、疑问或者补充欢迎大家留言或私信我。欢迎关注我的微信公众号《鱼仔ly》与我交流,每天进步一点点。


相关文章
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
23天前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
36 3
|
6天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
18天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
96 38
|
11天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
1天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
9 2
|
7天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
26 4
|
8天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
41 4
|
1月前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
79 1
Java面试题之Java集合面试题 50道(带答案)
|
18天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
54 4