java线程池

简介: java线程池

1. 什么是线程池

线程池简单理解就是一个池子,里面存放着已经创建好的线程,当有任务提交到线程池里面来的时候,池子中的某个线程就会主动去执行该任务,当提交过来的任务比较多,池子中的线程数不够用时,会自动扩充新的线程到线程池子,最多扩充到配置的最大线程个数,而当任务比较少时,池子中的线程个数就自动回收,把线程资源释放掉;并且通常情况下,为了能缓存提交过来的未被处理的任务,就需要有一个任务队列来存放,这就是一个线程池。

2.为什么要有线程池?

我们知道创建和销毁一个对象是很费时间的,特别是一些比较耗费资源的对象的创建和销毁,比如创建数据库连接,创建网络连接,创建线程等,所以就出现“池化技术”,即复用已经创建的对象,那么这样做能够带来 3 个好处:

(1)降低资源消耗,通过复用已经创建的线程降低线程创建和销毁造成的系统资源消耗;

(2)提高性能,当执行大量异步任务时线程池能够提供更好的性能,在不使用线程池时,每

当需要执行异步任务时直接 new 一个线程来运行,而线程的创建和销毁是需要开销的,而线

程池里面的线程是可复用的,不需要每次执行异步任务时都重新创建和销毁线程,直接执行任

务即可;

(3)方便线程管理,线程是不能随随便滥用的,当不停地创建线程可能导致系统资源消耗殆

尽而崩溃,使用线程池可以限制创建的线程个数、动态新增线程数量等,提高了线程的可管理

3.Java 线程池之 Executor 框架

(1)为了实现线程池和管理线程池,JDK 给我们提供了基于 Executor 接口的一系列接口、抽象类、实现类,我们把它称作线程池的 Executor 框架,Executor 框架本质上是一个线程池;

(2)Java 线程(java.lang.Thread)被一对一映射为本地操作系统内核线程,Java 线程启动时会创建一个本地操作系统线程,操作系统会调度所有线程并将它们分配给可用的 CPU 执行,当该 Java 线程终止时,这个操作系统线程也会被回收;实际上这是两层线程调度模型:

①上层 Java 线程的调度由 Executor 框架调度;

②下层操作系统的线程调度由操作系统调度;

(3)Java 的线程是这么设计的,包含两部分:

①工作任务;(Runnable 和 Callable)

②执行机制;(Thread、Executor 框架)

3.1 Executor 框架的接口与类结构

java.util.concurrent (并发编程的工具) juc

java.util.concurrent.atomic (变量的线程安全的原子性操作)

java.util.concurrent.locks (用于锁定和条件等待同步等)

①Executor:顶级接口,提交任务

②ExecutorService:扩展了Executor接口,提供了submit等api

③ForkJoinPool:工作窃取,补充之前的线程池

④ThreadPoolExecutor:自定义线程池的时候使用

⑤java.util.concurrent.Executors: 有静态方法,可以创建线程池,返回线程池对象

3.2 线程池的 7 大参数

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

(1)int corePoolSize, 线程池中的核心线程数量,即线程池中维护的最小的线程数量,即使这些线程处于空闲状态,它们也不会被销毁,除非设置了 allowCoreThreadTimeOut;默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程;在实际中如果需要线程池创建之后立即创建线程,可以通过以下两种方式:

①boolean prestartCoreThread(),初始化一个核心线程;

private ThreadPoolExecutor executor = new ThreadPoolExecutor(100,300,1000, 
  TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(50), 
  new ThreadFactoryBuilder().setNamePrefix("Line-Control-").build(),
  new ThreadPoolExecutor.DiscardOldestPolicy());
 boolean b = executor.prestartCoreThread();

②int prestartAllCoreThreads(),初始化所有核心线程;

(2)BlockingQueue<Runnable> workQueue, 任务队列,当核心线程全部繁忙时,提交的 Runnable 任务存放到该任务队列中,等待被核心线程来执行

(3)int maximumPoolSize, 线程池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到 maximumPoolSize 这个上限;

(4)long keepAliveTime, 线程空闲超时时间,如果一个线程处于空闲状态,并且当前的线程数量大于 corePoolSize,

那么在指定时间后,这个空闲线程会被销毁;

(5)TimeUnit unit

keepAliveTime 的时间单位 (天、小时、分、秒…)

(6)ThreadFactory threadFactory, 线程工厂,用于创建线程,一般采用默认的Executors.defaultThreadFactory()即可,也可以自定义实现

(7)RejectedExecutionHandler handler, 拒绝策略(饱和策略),当任务太多来不及处理时,就需要进行任务拒绝。任务拒绝是线程池的保护措施,当核心线程 corePoolSize 正在执行任务、线程池的任务队列

workQueue 已满、并且线程池中的线程数达到 maximumPoolSize 时,就需要“拒绝”掉新提交

过来的任务;

JDK 提供了 4 种内置的拒绝策略:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy 和

DiscardPolicy;

①AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常,这是线程池默认

的拒绝策略,在任务不能再提交的时候抛出异常,让开发人员及时知道程序运行状态,这样能

在系统不能承载更大的并发量时,及时通过异常信息发现;

②DiscardPolicy:直接丢弃任务,不抛出异常,使用此策略可能会使我们无法发现系统的异

常状态,建议一些无关紧要的业务采用此策略;

③DiscardOldestPolicy:丢弃任务队列中靠最前的任务,并执行当前任务,是否要采用此拒绝

策略,根据实际业务是否允许丢弃老任务来评估和衡量;

④CallerRunsPolicy: 交由任务的调用线程(提交任务的线程,如main线程)来执行当前任务;这种拒绝策

略会让所有任务都能得到执行,适合大量计算类型的任务执行,使用这种策略的最终目标是要让每个任务都能执行完毕,而使用多线程执行计算任务只是作为增大吞吐量的手段;

除了上面的四种拒绝策略,还可以通过实现 RejectedExecutionHandler 接口,实现自定义的拒绝策略

3.3 ExecutorService接口常用方法

(1)void shutdown() 启动一次顺序关闭,执行以前提交的任务,不能再提交新的任务了。

(2)List<Runnable> shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

(3)<T> Future<T> submit(Callable<T> task) 执行带返回值的任务,返回一个Future对象。

(4)Future<?> submit(Runnable task) 执行 Runnable 任务,并返回一个表示该任务的 Future。

(5)<T> Future<T> submit(Runnable task, T result) 执行 Runnable 任务,并返回一个表示该任务的 Future。

4. 线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

5.JDK内置线程池ExecutorService实现类

获取ExecutorService可以利用JDK中的Executors 类中的静态方法,常用获取方式如下:

5.1 newFixedThreadPool

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

①核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间

②阻塞队列是无界的,可以放任意数量的任务

③适用于任务量已知,相对耗时的任务

5.2 newCachedThreadPool

创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建

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

5.3 newSingleThreadExecutor

创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程

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

(1)使用场景:希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。

(2)自己创建的一个单线程和newSingleThreadExecutor区别

自己创建的一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施(任务队列中的其他任务将无法执行),而线程池还会新建一个线程,保证池的正常工作

(3)newFixedThreadPool(1)和newSingleThreadExecutor的区别

①Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改;FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法

②Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改;对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

6. 线程池工作流程

目录
相关文章
|
1月前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4月前
|
Java 调度 数据库
Java并发编程:深入理解线程池
在Java并发编程的海洋中,线程池是一艘强大的船,它不仅提高了性能,还简化了代码结构。本文将带你潜入线程池的深海,探索其核心组件、工作原理及如何高效利用线程池来优化你的并发应用。
|
4月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
135 1
|
4月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
4月前
|
存储 监控 安全
一天十道Java面试题----第三天(对线程安全的理解------>线程池中阻塞队列的作用)
这篇文章是Java面试第三天的笔记,讨论了线程安全、Thread与Runnable的区别、守护线程、ThreadLocal原理及内存泄漏问题、并发并行串行的概念、并发三大特性、线程池的使用原因和解释、线程池处理流程,以及线程池中阻塞队列的作用和设计考虑。
|
24天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
20天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
1月前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
113 38
|
27天前
|
存储 缓存 监控
Java中的线程池深度解析####
本文深入探讨了Java并发编程中的核心组件——线程池,从其基本概念、工作原理、核心参数解析到应用场景与最佳实践,全方位剖析了线程池在提升应用性能、资源管理和任务调度方面的重要作用。通过实例演示和性能对比,揭示合理配置线程池对于构建高效Java应用的关键意义。 ####
|
1月前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
101 4