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. 线程池工作流程

目录
相关文章
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
3月前
|
Java 调度 数据库
Java并发编程:深入理解线程池
在Java并发编程的海洋中,线程池是一艘强大的船,它不仅提高了性能,还简化了代码结构。本文将带你潜入线程池的深海,探索其核心组件、工作原理及如何高效利用线程池来优化你的并发应用。
|
3月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
113 1
|
3月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
3月前
|
存储 监控 安全
一天十道Java面试题----第三天(对线程安全的理解------>线程池中阻塞队列的作用)
这篇文章是Java面试第三天的笔记,讨论了线程安全、Thread与Runnable的区别、守护线程、ThreadLocal原理及内存泄漏问题、并发并行串行的概念、并发三大特性、线程池的使用原因和解释、线程池处理流程,以及线程池中阻塞队列的作用和设计考虑。
|
18天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
96 38
|
18天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
54 4
|
18天前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
80 2
|
20天前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用
|
2月前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。