Java语法进阶及常用技术(八)--线程池

news2024/11/17 21:18:10

初识线程池

什么是“池”
---- 软件中的“池”,可以理解为计划经济。
我们的资源是有限的,比如只有十个线程,我们创造十个线程的线程池,可能我们的任务非常多,如1000个任务,我们就把1000个任务放到我们十个线程中去,慢慢的去执行,最终会执行完所有任务。但是资源总量十个线程是被控制住的,还有就是不需要再创建更多的线程了,创建线程是有一定的开销的。所以我们复用线程池的这十个线程,每个线程的执行效率也会提高。

如果不适用线程池,每个任务都新开一个线程处理

  • 一个线程
  • for循环创建线程
  • 当任务数量上升到1000

这样开销太大,我们希望有固定数量的线程,来执行这1000个任务,这样就避免了反复创建并销毁线程所带来的开销问题。

为什么要使用线程池

  • 问题一:反复创建线程开销大
  • 问题二:过多的线程会占用太多内存
  • 解决以上两个问题的思路:
    • 用少量的线程 ----避免内存占用过多
    • 让这部分线程都保持工作,且可以反复执行任务 ---- 避免生命周期的损耗。

线程池的好处

  • 加快响应速度
  • 合理利用CPU和内存
  • 统一管理

线程池适合应用的场合

  • 服务器接收到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。
  • 实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理。

创建和停止线程池

线程池构造方法的参数
在这里插入图片描述

  • corePoolSize指的是核心线程数
    -线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务。
  • 最大量maxPoolSize
    -在核心线程数的基础上,额外增加的线程数的上限。
    在这里插入图片描述
    添加线程规则
  • 如果线程数小于corePoolSize,创建一个新线程来运行新任务。
  • 如果线程数等于(或大于)corePoolSize但少于workQueue上限,则将任务放入队列。
  • 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程。
  • 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝。
    在这里插入图片描述
    增减线程的特点
  • 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
  • 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
  • 通过设置maximumPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务。
  • 只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize。

keepAliveTime
如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止。

ThreadFactory用来创建线程

  • 默认使用Executors.defaultThreadFactory()
  • 创建出来的线程都在同一个线程组
  • 如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。

工作队列
有3种最常见的队列类型:

  • 直接交换:SynchronousQueue
  • 无界队列:LinkedBlockingQueue
  • 有界的队列:ArrayBlockingQueue

线程池该手动创建还是自动创建

手动创建更好,因为这样可以更加明确线程池的运行规则,避免资源耗尽的风险。
自动创建线程池(即直接调用JDK封装好的构造方法)可能带来哪些问题?
FixedThreadPool
源码为

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

可看出corePoolSize和maximumPoolSize相同,且keepAliveTime为0,工作队列为LinkedBlockingQueue无界队列。
容易造成大量内存占用,可能会导致OOM。
在这里插入图片描述

SingleThreadExecutor
源码为

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

可看出corePoolSize和maximumPoolSize相同且为1,且keepAliveTime为0,工作队列为LinkedBlockingQueue无界队列。
当请求堆积的时候,可能会占用大量的内存。

CachedThreadPool
可缓存线程池
特点:具有自动回收多余线程的功能
在这里插入图片描述
源码为

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

弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM

ScheduledThreadPool
支持定时及周期性任务执行的线程池
在这里插入图片描述
以上4种线程池的构造方法的参数
在这里插入图片描述

正确的创建线程池的方法

  • 根据不同的业务场景,设置线程池参数。
  • 比如:内存有多大,给线程取什么名字等等

线程池里的线程数量设定为多少比较合适?

  • CPU密级型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍左右。
  • 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数很多倍。

参考Brain Goetz推荐的计算方法:
在这里插入图片描述
workStealingPool是JDK1.8加入的
子任务:比如二叉树的遍历
窃取:子任务会被放到每个线程独有的任务队列,而不是公共队列。比如有个线程产生了很多子任务,其他线程如果是空闲,会帮助第一个线程,窃取它的子任务去执行。这个任务最好不能加锁,也不能保证执行顺序。

停止线程池的正确方法

  • shutdown :运行了这个方法,线程池有新的任务会拒绝并抛出拒绝的异常,会把正在执行的任务和队列里等待的任务都执行完毕后关闭线程池。
  • isShutdown:返回线程池是否有停止标记(是否执行过shutdown)
  • isTerminated:返回线程池是否完全终止
  • awaitTermination:等待一段时间检测线程是否终止
  • shutdownNow:立刻关闭线程池,并返回未执行的任务列表。

暂停和恢复线程池

任务太多,怎么拒绝?

  • 拒绝时机
    • 当Executor关闭是,提交新任务会被拒绝。
    • 以及当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时。

4种拒绝策略

  • AbortPolicy 直接抛出异常
  • DiscardPolicy 默默地把任务丢弃,不会通知
  • DiscardOldestPolicy 丢弃最老的任务
  • CallerRunsPolicy 谁提交的任务,让谁执行

钩子方法
给线程池加点料
每个任务执行前后、日志、统计等场景使用

package com.ql;

import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示每个任务执行前后放钩子函数
 */
public class PauseableThreadPool extends ThreadPoolExecutor {
    private boolean isPaused;
    private final ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    /**
     * 默认重写,不用管
     */
    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    /**
     * 默认重写,不用管
     */
    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    /**
     * 默认重写,不用管
     */
    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    /**
     * 默认重写,不用管
     */
    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    /**
	 * 重写该方法,在任务执行前放钩子函数
     */
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while (isPaused){
                condition.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
	
    private void pause(){
        lock.lock();
        try{
            isPaused = true;
        }finally {
            lock.unlock();
        }
    }

    public void resume(){
        lock.lock();
        try{
            isPaused = false;
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("我被执行");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 200; i++) {
            pauseableThreadPool.execute(runnable);
        }
        Thread.sleep(1500);
        pauseableThreadPool.pause();
        System.out.println("线程池被暂停了~~");
        Thread.sleep(1500);
        pauseableThreadPool.resume();
        System.out.println("线程池被恢复了~~");
    }
}
/**执行结果
...
我被执行
我被执行
我被执行
我被执行
线程池被暂停了~~
线程池被恢复了~~
我被执行
我被执行
我被执行
我被执行
...
*/

线程池实现原理

线程池组成部分

  • 线程池管理器:创建停止线程池等
  • 工作线程:创建出来执行任务的线程
  • 任务队列:存放未执行任务的队列,必须支持并发
  • 任务接口(Task)

在这里插入图片描述
线程池相关类和接口关系
在这里插入图片描述
Executors是Java提供的工具类,包含了很多自带的线程池。
线程池实现任务复用的原理

  • 相同线程执行不同任务

线程池状态

在这里插入图片描述
使用线程池的注意点

  • 避免任务堆积
  • 避免线程数过度增加
  • 排查线程泄漏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

shell脚本学习记录(流程控制)

前言&#xff1a; 在shell脚本中&#xff0c;()、{}、[]都是用来表示命令或者变量的范围或者属性。它们的具体区别如下&#xff1a; ()&#xff1a;表示命令在子shell中运行。括号中的命令会在一个子shell中运行&#xff0c;并且该子shell拥符有自己的环境变量和文件描述&#…

【youcans动手学模型】DenseNet 模型-CIFAR10图像分类

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【youcans动手学模型】DenseNet 模型-CIFAR10图像分类 1. DenseNet 神经网络模型1.1 模型简介1.2 论文介绍1.3 改进方法与后续工作1.4 分析与讨论 2. 在 PyTorch 中定义 DenseNet 模型类2.1 DenseBlo…

性能测试实战——登录接口的性能测试(超详细总结)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 在实际业务场景中…

python:六个模块,概括全书(上万字最详细版)

拍摄于——无锡南长街 文章目录 模块一&#xff1a;基础知识1、python语言2、常见数字类型3、字符串4、数字类型转换5、标识符命名6、常见关键字7、运算符与表达式&#xff08;1&#xff09;算术运算符&#xff08;2&#xff09;关系运算符&#xff08;3&#xff09;逻辑运算符…

循序渐进,搞懂什么是动态规划

循序渐进&#xff0c;搞懂什么是动态规划 写在前面 温馨提示&#xff0c;本文的篇幅很长&#xff0c;需要花很长的时间阅读。如果要完全理解所有内容&#xff0c;还需要花更多的时间学习。如果打算认真学习动态规划&#xff0c;又不能一次看完&#xff0c;建议您收藏本文以便后…

《深入理解计算机系统》(6)存储器层次结构

1、存储技术 随机访问存储器&#xff0c;分为两类&#xff1a; RAM&#xff0c;同时也是易失性存储器&#xff0c;也分为两类&#xff1a; - SRAM&#xff1a;静态随机访问存储器&#xff0c;速度快&#xff0c;价格高。多用来作为高速缓存存储器。 - DRAM&#xff1a;动态随机…

WinDbg安装入坑1(C#)

由于作者水平有限&#xff0c;如有写得不对的地方&#xff0c;请指正。 使用WinDbg的过程中&#xff0c;坑特别的多&#xff0c;对版本要求比较严格&#xff0c;如&#xff1a; 1 32位应用程序导出的Dump文件要用32位的WinDbg打开&#xff0c;想要没有那么多的问题&#xff…

chatgpt赋能python:Python删除内容:掌握三种删除方式

Python删除内容&#xff1a;掌握三种删除方式 删除变量中的值 删除变量中的值是Python编程中常见的操作。Python提供了del语句用于删除变量中的值&#xff1a; x "Hello World" del x上述代码中&#xff0c;del x语句将删除变量x中的值。如果我们在执行print(x)时…

从C语言到C++_18(stack和queue的常用函数+相关练习)力扣

目录 1. stack 1.1 栈的概念 1.2 stack 的介绍和使用 2. queue 2.1 队列的概念 2.2 queue 的介绍和使用 3. 栈和队列的相关选择题 答案&#xff1a; 4. 栈和队列的相关OJ题 155. 最小栈 - 力扣&#xff08;LeetCode&#xff09; 解析代码&#xff1a; 剑指 Offer 3…

python学习-代码调试器

目录 为什么学习调试器Pycharm Debugger示例所用代码布局调试工具栏 Debug Bar程序控制工具栏 pdb查看源代码 l list查看当前函数源代码 ll longlist打印变量 p查看调用栈w where向上移动当前帧 u up向上移动当前帧 d down运行当前行代码,在第一个可以停止的位置停下 s step继续…

Selenium基础篇之八大元素定位方式

文章目录 前言一、如何进行元素定位&#xff1f;1.右击元素-检查2.F12-选择工具点击元素3.借助selenium IDE 二、八大元素定位方式1.ID1.1 方法1.2 举例1.3 代码1.4 截图 2.NAME2.1 方法2.2 举例2.3 代码2.4 截图 3.CLASS_NAME3.1 方法3.2 举例3.3 代码3.4 截图 4.TAG_NAME4.1 …

再也不用担心组件跨层级的数据共享和方法驱动了

文章目录 兄弟组件的传值和方法调用多个独立组件的数据共享和方法调用多个组件内的方法和数据互相驱动&#xff1a;eventBus多个组件的数据共享以及数据修改&#xff1a;vuex 项目中关于组件的使用经常会碰到这种情况&#xff1a;父子组件传和方法调用、兄弟组件的传值和方法调…

Selenium该如何实现微博自动化运营:关注、点赞、评论

目录 前言&#xff1a; Selenium 是什么&#xff1f; 一、核心代码 二、步骤分解 三、自动化运营常用工具 前言&#xff1a; 使用 Selenium 实现微博自动化运营&#xff0c;可以提高效率、减少工作量&#xff0c;下面讲解如何使用 Selenium 实现微博的关注、点赞和评论功…

webSocket 学习

引子 WebSocket 是一种在单个 TCP 连接上进行全双工通信的网络协议。它是 HTML5 中的一种新特性&#xff0c;能够实现 Web 应用程序和服务器之间的实时通信&#xff0c;比如在线聊天、游戏、数据可视化等。 相较于 HTTP 协议的请求-响应模式&#xff0c;使用 WebSocket 可以建…

JVM零基础到高级实战之Java内存区域虚拟机栈

JVM零基础到高级实战之Java内存区域虚拟机栈 JVM零基础到高级实战之Java内存区域虚拟机栈 文章目录 JVM零基础到高级实战之Java内存区域虚拟机栈前言JVM内存模型之虚拟机栈总结 前言 JVM零基础到高级实战之Java内存区域虚拟机栈 JVM内存模型之虚拟机栈 虚拟机栈是什么&#x…

Ansys Lumerical | 光纤布拉格光栅温度传感器的仿真模拟

说明 该示例演示了一种基于光纤布拉格光栅&#xff08;FBG&#xff09;的温度传感器&#xff0c;因为光纤折射率会随温度而变化&#xff0c;导致其布拉格波长发生偏移&#xff0c;所以可以被用作温度的测量。&#xff08;联系我们获取文章附件&#xff09; 综述 在本示例中要考…

java八股文-并发篇

并发篇 1. 线程状态 要求 掌握 Java 线程六种状态掌握 Java 线程状态转换能理解五种状态与六种状态两种说法的区别 六种状态及转换 分别是 新建 当一个线程对象被创建&#xff0c;但还未调用 start 方法时处于新建状态此时未与操作系统底层线程关联 可运行 调用了 start …

在Linux上安装Zookeeper集群(zookeeper-3.5.9)

记录&#xff1a;455 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;使用zookeeper-3.5.9版本&#xff0c;在三台机器上&#xff0c;安装Zookeeper集群。 版本&#xff1a;zookeeper-3.5.9&#xff0c;CentOS 7.9,Linux kernel-5.4.218。 1.主机规划 目标&#xff1a…

接口自动化测试新玩法!Python构建mock服务让你的测试更加高效!

目录 引言 Flask mock接口开发示例 引言 Mock 即模拟&#xff0c;就是在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;用一个虚拟的对象来创建以便测试的测试方法&#xff0c;其最大的优势就是降级前后端耦合度&#xff0c; 使前端工程师可以不…

Alloy Tutorial(1)Alloy 基本使用

文章目录 构造一个 graph谓词undirected 无向图undirected2 无向图的第二种写法assertFact扩展 构造一个 graph In this workshop we are going to use Alloy to model graphs. Mathematically, recall that a graph is just a pair ⟨V, E⟩ where V is a set of vertices (a…