多线程2,线程池深入理解

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

目录介绍

  • 1.ThreadPoolExecutor类介绍
  • 1.1 构造函数
  • 1.2 参数解析
  • 1.3 遵循的规则
  • 1.4 使用线程池管理线程的优点
  • 2.关于线程池的分类
  • 2.1 FixedThreadPool
  • 2.2 CachedThreadPool
  • 2.3 ScheduledThreadPool
  • 2.4 SingleThreadExecutor
  • 3.线程池一般用法
  • 3.1 一般方法介绍
  • 3.2 newFixedThreadPool的使用
  • 3.3 newSingleThreadExecutor的使用
  • 3.4 newCachedThreadPool的使用
  • 3.5 newScheduledThreadPool的使用
  • 3.6 线程创建规则
  • 4.线程池封装
  • 4.1 具体可以参考下篇文章
  • 4.2 参考博客

前言介绍

1.ThreadPoolExecutor类介绍

1.1 构造函数

  • ExecutorService是最初的线程池接口,ThreadPoolExecutor类是对线程池的具体实现,它通过构造方法来配置线程池的参数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

1.2 参数解析

  • corePoolSize,线程池中核心线程的数量,默认情况下,即使核心线程没有任务在执行它也存在的,我们固定一定数量的核心线程且它一直存活这样就避免了一般情况下CPU创建和销毁线程带来的开销。我们如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程就会有超时策略,这个时间由keepAliveTime来设定,即keepAliveTime时间内如果核心线程没有回应则该线程就会被终止。allowCoreThreadTimeOut默认为false,核心线程没有超时时间。
  • maximumPoolSize,线程池中的最大线程数,当任务数量超过最大线程数时其它任务可能就会被阻塞。最大线程数=核心线程+非核心线程。非核心线程只有当核心线程不够用且线程池有空余时才会被创建,执行完任务后非核心线程会被销毁。
  • keepAliveTime,非核心线程的超时时长,当执行时间超过这个时间时,非核心线程就会被回收。当allowCoreThreadTimeOut设置为true时,此属性也作用在核心线程上。
  • unit,枚举时间单位,TimeUnit。
  • workQueue,线程池中的任务队列,我们提交给线程池的runnable会被存储在这个对象上。

1.3 遵循的规则

  • 当线程池中的核心线程数量未达到最大线程数时,启动一个核心线程去执行任务;
  • 如果线程池中的核心线程数量达到最大线程数时,那么任务会被插入到任务队列中排队等待执行;
  • 如果在上一步骤中任务队列已满但是线程池中线程数量未达到限定线程总数,那么启动一个非核心线程来处理任务;
  • 如果上一步骤中线程数量达到了限定线程总量,那么线程池则拒绝执行该任务,且ThreadPoolExecutor会调用RejectedtionHandler的rejectedExecution方法来通知调用者。

1.4 使用线程池管理线程的优点

  • 1、线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销
  • 2、线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量
  • 3、在执行大量异步任务时提高了性能
  • 4、Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等

2.关于线程池的分类

2.1 FixedThreadPool

  • 通过Executors的newFixedThreadPool()方法创建,它是个线程数量固定的线程池,该线程池的线程全部为核心线程,它们没有超时机制且排队任务队列无限制,因为全都是核心线程,所以响应较快,且不用担心线程会被回收。

2.2 CachedThreadPool

  • 通过Executors的newCachedThreadPool()方法来创建,它是一个数量无限多的线程池,它所有的线程都是非核心线程,当有新任务来时如果没有空闲的线程则直接创建新的线程不会去排队而直接执行,并且超时时间都是60s,所以此线程池适合执行大量耗时小的任务。由于设置了超时时间为60s,所以当线程空闲一定时间时就会被系统回收,所以理论上该线程池不会有占用系统资源的无用线程。

2.3 ScheduledThreadPool

  • 通过Executors的newScheduledThreadPool()方法来创建,ScheduledThreadPool线程池像是上两种的合体,它有数量固定的核心线程,且有数量无限多的非核心线程,但是它的非核心线程超时时间是0s,所以非核心线程一旦空闲立马就会被回收。这类线程池适合用于执行定时任务和固定周期的重复任务。

2.4 SingleThreadExecutor

  • 通过Executors的newSingleThreadExecutor()方法来创建,它内部只有一个核心线程,它确保所有任务进来都要排队按顺序执行。它的意义在于,统一所有的外界任务到同一线程中,让调用者可以忽略线程同步问题。

3.线程池一般用法

3.1 一般方法介绍

  • shutDown(),关闭线程池,需要执行完已提交的任务;
  • shutDownNow(),关闭线程池,并尝试结束已提交的任务;
  • allowCoreThreadTimeOut(boolen),允许核心线程闲置超时回收;
  • execute(),提交任务无返回值;
  • submit(),提交任务有返回值;

3.2 newFixedThreadPool的使用

  • 3.2.1 创建一个newFixedThreadPool线程池
private void newFixedThreadPool() {
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
    for (int i = 1; i <= 20; i++) {
        final int index = i;
        fixedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.e("潇湘剑雨", "线程:"+threadName+",正在执行第" + index + "个任务");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
  • 3.2.2 打印日志如下
  • 创建了一个线程数为5的固定线程数量的线程池,同理该线程池支持的线程最大并发数也是5,模拟20个任务让它处理,执行任务。最后我们获取线程的信息,打印日志。
  • 日志如图所示:
    image

3.3 newSingleThreadExecutor的使用

  • 3.3.1 创建一个newSingleThreadExecutor线程池
private void newSingleThreadExecutor() {
    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
    for (int i = 1; i <= number; i++) {
        final int index = i;
        singleThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.v("潇湘剑雨", "线程:"+threadName+",正在执行第" + index + "个任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
  • 3.3.2 打印日志如下
  • 改了线程池的实现方式,即依次一个一个的处理任务,而且都是复用一个线程,日志为
    image

3.4 newCachedThreadPool的使用

  • 3.4.1 创建一个newCachedThreadPool线程池
private void newCachedThreadPool() {
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    for (int i = 1; i <= number; i++) {
        final int index = i;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                Log.v("潇湘剑雨newCachedThreadPool", "线程:" + threadName + ",正在执行第" + index + "个任务");
                try {
                    long time = index * 500;
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
  • 3.4.2 打印日志如下
  • 为了体现该线程池可以自动根据实现情况进行线程的重用,而不是一味的创建新的线程去处理任务,我设置了每隔1s去提交一个新任务,这个新任务执行的时间也是动态变化的,所以,效果为:
    image

3.5 newScheduledThreadPool的使用

  • 3.5.1 创建一个newScheduledThreadPool线程池
private void newScheduledThreadPool() {
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    //延迟2秒后执行该任务
    scheduledThreadPool.schedule(new Runnable() {
        @SuppressLint("LongLogTag")
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            Log.e("潇湘剑雨newScheduledThreadPool", "线程:" + threadName + ",正在执行");
        }
    }, 2, TimeUnit.SECONDS);
    //延迟1秒后,每隔2秒执行一次该任务
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            Log.e("潇湘剑雨", "线程:" + threadName + ",正在执行");
        }
    }, 1, 2, TimeUnit.SECONDS);
}
  • 3.5.2 打印日志如下
  • 通过日志可以发现schedule方法的任务只是执行了一次,然后每隔2秒执行一次该scheduleAtFixedRate方法中的任务
    image

3.6 线程创建规则

  • ThreadPoolExecutor对象初始化时,不创建任何执行线程,当有新任务进来时,才会创建执行线程。构造ThreadPoolExecutor对象时,需要配置该对象的核心线程池大小和最大线程池大小
    1. 当目前执行线程的总数小于核心线程大小时,所有新加入的任务,都在新线程中处理。
    1. 当目前执行线程的总数大于或等于核心线程时,所有新加入的任务,都放入任务缓存队列中。
    1. 当目前执行线程的总数大于或等于核心线程,并且缓存队列已满,同时此时线程总数小于线程池的最大大小,那么创建新线程,加入线程池中,协助处理新的任务。
    1. 当所有线程都在执行,线程池大小已经达到上限,并且缓存队列已满时,就rejectHandler拒绝新的任务。

4.线程池封装

4.1 具体可以参考下篇文章

4.2 参考博客

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
5月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
223 0
|
2月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
178 2
|
5月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
10月前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
471 60
【Java并发】【线程池】带你从0-1入门线程池
|
6月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
404 5
|
8月前
|
Java
线程池是什么?线程池在实际工作中的应用
总的来说,线程池是一种有效的多线程处理方式,它可以提高系统的性能和稳定性。在实际工作中,我们需要根据任务的特性和系统的硬件能力来合理设置线程池的大小,以达到最佳的效果。
245 18
|
11月前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
10月前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
380 20
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
730 64
|
10月前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。

热门文章

最新文章