当前位置: 首页 > article >正文

线程池实战——数据库连接池

引言

作者在前面写了很多并发编程知识深度探索系列文章,反馈得知友友们收获颇丰,同时我也了解到友友们也有了对知识如何应用感到很模糊的问题。所以作者就打算写一个实战系列文章,让友友们切身感受一下怎么应用知识。话不多说,开始吧!

在当今数据驱动的时代,数据库作为应用程序的核心组成部分,其连接的高效管理直接影响着系统的性能与稳定性。尤其是在高并发场景下,频繁创建和销毁数据库连接会带来巨大的资源开销,严重制约系统的响应速度和吞吐量。数据库连接池技术应运而生,通过预先创建并管理一定数量的连接,实现连接的复用,极大提升了资源利用效率。本文将通过一个基于 Java 的示例,深入剖析如何运用 CountDownLatch、等待超时模式与动态代理等技术,构建线程安全的数据库连接池,为高并发环境下的资源管理提供清晰的实践路径与解决方案。​

实战前置知识

1.CountDownLatch

CountDownLatch 是 JUC 中的一个同步工具类,用于协调多个线程之间的同步,确保主线程在多个子线程完成任务后继续执行。下面这篇博客文章也有讲过《并发工具类》CountDownLatch 并发编程:各种锁机制、锁区别、并发工具类深刻总结-CSDN博客

CountDownLatch 它的核心思想是通过一个倒计时计数器来控制多个线程的执行顺序。

class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {int threadCount = 3;CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {try {Thread.sleep((long) (Math.random() * 1000)); // 模拟任务执行System.out.println(Thread.currentThread().getName() + " 执行完毕");} catch (InterruptedException e) {e.printStackTrace();} finally {latch.countDown(); // 线程完成后,计数器 -1}}).start();}latch.await(); // 主线程等待System.out.println("所有子线程执行完毕,主线程继续执行");}
}

在使用的时候,我们需要先初始化一个 CountDownLatch 对象,指定一个计数器的初始值,表示需要等待的线程数量。然后在每个子线程执行完任务后,调用 countDown() 方法,计数器减 1。接着主线程调用 await() 方法进入阻塞状态,直到计数器为 0,也就是所有子线程都执行完任务后,主线程才会继续执行。

例如《秦二爷:王者荣耀等待玩家确认》例子。

以王者荣耀为例,我们来创建五个线程,分别代表大乔、兰陵王、安其拉、哪吒和铠。每个玩家都调用 countDown() 方法,表示已就位。主线程调用 await() 方法,等待所有玩家就位。

2.等待超时模式

2.1等待/通知模式的经典范式

等待/通知的经典范式,该范式分为两部分,分别针对等待方(消费者)和通知方(生产者)。

等待方遵循如下原则。

  1. 获取对象的锁。
  2. 如果条件不满足,那么调用对象的 wait()方法,被通知后仍要检查条件
  3. 条件满足则执行对应的逻辑。

对应的伪代码如下。

synchronized (对象) {while (条件步满足) {对象.wait();}对应的处理逻辑
}

通知方遵循如下原则。

  1. 获得对象的锁。
  2. 改变条件。
  3. 通知所有等待在对象上的线程。

对应的伪代码如下

synchronized(对象) {改变条件对象.notifyAll();
}

2.2等待超时模式

开发人员经常会遇到这样的方法调用场景:调用一个方法时等待一段时间(一般来说是给定一个时间段),如果该方法能够在给定的时间段得到结果,那么立刻将结果返回,反之超时返回默认结果。

前面介绍了等待/通知的经典范式,即加锁、条件循环和处理逻辑3个步骤,但这种范式无法做到超时等待。要想支持超时等待,只需要对经典范式做非常小的改动,改动内容如下所示。

假设超时时间段是T,那么可以推断出在当前时间now+T之后就会超时。定义如下变量口。

  1. 等待持续时间:REMAINING=T。
  2. 超时时间:FUTURE=now+T。

这时仅需要执行 wait(REMAINING),在 wait(REMAINING)返回后将执行REMAINING=FUTURE-NOW。如果REMAINING小于或等于0,表示已经超时,直接退出,否则将继续执行 wait(REMAINING)。
上述描述等待超时模式的伪代码如下。

//对当前对象加锁
public synchronized Object get(long mills) throws InterruptedException {long future = System.currentTimeMillis() + mills;long remaining = mills;//超时大于0并且result返回值不满足要求while ((result == null) && remaining > 0) {wait(remaining);remaining = future - System.currentTimeMillis();}return result;
}

可以看出,等待超时模式就是在等待/通知的经典范式的基础上增加了超时控制,这使得该模式相比原有范式更具灵活性,因为即使方法的执行时间过长,也不会“永久”阻塞调用者,而是会按照调用者的要求“按时”返回。

 实战开始—>数据库连接池示例

我们使用等待超时模式来构造一个简单的数据库连接池,模拟从连接池中获取、使用和释放连接的过程,而客户端获取连接的过程被设定为等待超时的模式,也就是在1000ms内如果无法获取到可用连接,将会返回给客户端一个null。设定连接池的大小为10个,然后通过调节客户端的线程数来模拟无法获取连接的场景。
首先看一下连接池的定义。它通过构造函数初始化连接的最大上限,通过一个双向队列来维护连接,调用着需要先调用fetchConnection(long)方法来指定在多少ms内超时获取连接、当连接使用完成后,需要调用releaseConnection(Conneetion)方法将连接放回线程池。
示例代码如下

public class ConnectionPool {private LinkedList<Connection> pool = new LinkedList<Connection>();public ConnectionPool(int initialSize) {if (initialSize <= 0) throw new IllegalArgumentException();for (int i = 0; i < initialSize; i++) {pool.addLast(ConnectionDriver.createConnection());}}public void releaseConnection(Connection connection) {if (connection != null) {synchronized (pool) {pool.addLast(connection);pool.notifyAll();}}}public Connection fetchConnection(long mills) throws InterruptedException {synchronized (pool) {if (mills <= 0) {while (pool.isEmpty()) {pool.wait();}return pool.removeFirst();} else {long future = System.currentTimeMillis() + mills;long remaining = mills;while (pool.isEmpty() && remaining > 0) {pool.wait(remaining);remaining = future - System.currentTimeMillis();}Connection result = null;if (!pool.isEmpty()) {result = pool.removeFirst();}return result;}}}
}

由于java.sql.Connection是一个接口,最终的实现是由数据库驱动提供方来实现的,考虑到这只是个示例,我们通过动态代理构造了一个Connection,该Connection的代理实现仅仅是在commitO)方法调用时休眠100ms,示例如下。

public class ConnectionDriver {static class ConnectionHandler implements InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("commit")) {TimeUnit.MILLISECONDS.sleep(100);}return null;}}public static final Connection createConnection() {return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),new Class<?>[] { Connection.class },new ConnectionHandler());}
}

下面通过一个示例来测试简易数据库连接池的工作情况。模拟客户端ConnectionRunner获取、使用、释放连接的过程,当它使用时将会增加获取到的连接的数量,反之,将会增加未获取到的连接的数量,示例如下。

public class ConnectionPoolTest {static ConnectionPool pool = new ConnectionPool(10);static CountDownLatch start = new CountDownLatch(1);static CountDownLatch end;public static void main(String[] args) throws Exception {int threadCount = 10;end = new CountDownLatch(threadCount);int count = 20;AtomicInteger got = new AtomicInteger();AtomicInteger notGot = new AtomicInteger();for (int i = 0; i < threadCount; i++) {Thread thread = new Thread(new ConnectionRunner(count, got, notGot), "ConnectionRunnerThread");thread.start();}start.countDown();end.await();System.out.println("total invoke: " + (threadCount * count));System.out.println("got connection: " + got);System.out.println("not got connection " + notGot);}static class ConnectionRunner implements Runnable {int count;AtomicInteger got;AtomicInteger notGot;public ConnectionRunner(int count, AtomicInteger got, AtomicInteger notGot) {this.count = count;this.got = got;this.notGot = notGot;}public void run() {try {start.await();} catch (Exception ex) {}while (count > 0) {try {Connection connection = pool.fetchConnection(1000);if (connection != null) {try {connection.createStatement();connection.commit();} finally {pool.releaseConnection(connection);got.incrementAndGet();}} else {notGot.incrementAndGet();}} catch (Exception ex) {} finally {count--;}}end.countDown();}}
}

上述示例中使用了CountDownLatch来确保ConnectionRunnerThread能够同时开始执并且在全部结束之后,才使main线程从等待状态中返回。当前设定的场景是10个线同时运行来获取连接池(10个连接)中的连接,通过调节线程数量来观察未获取到的连的情况。线程数量、总获取次数、获取到的数量、未获取到的数量以及未获取到的比率如表所示。不同电脑机器实际输出可能与此表不同。

线程数量

总获取次数

获取到的数量

未获取到的数量

未获取到的比率

10

200

200

0

0%

20

400

387

13

3.25%

30

600

542

58

9.67%

40

800

700

100

12.5%

50

1000

828

172

17.2%

从表中的数据统计可以看出,在资源一定的情况下(连接池中的10个连接),随着客户端线程的逐步增加,客户端出现超时无法获取连接的比率不断升高。虽然客户端线程在这种超时获取的模式下会出现连接无法获取的情况,但是它能够保证客户端线程不会一直挂在连接获取的操作上,而是“按时”返回,并告知客户端连接获取出现问题,这是系统的一种自我保护机制。数据库连接池的设计也可以复用到其他的资源获取场景,针对昂贵资源(比如数据库连接)的获取都应该进行超时限制。

相关文章:

线程池实战——数据库连接池

引言 作者在前面写了很多并发编程知识深度探索系列文章&#xff0c;反馈得知友友们收获颇丰&#xff0c;同时我也了解到友友们也有了对知识如何应用感到很模糊的问题。所以作者就打算写一个实战系列文章&#xff0c;让友友们切身感受一下怎么应用知识。话不多说&#xff0c;开…...

修改 vue-pdf 源码升级 pdfjs-dist 包, 以解决部分 pdf 文件显示花屏问题

文章目录 背景: 客户反馈有部分文件预览花屏 最终解决方案: 自己 fork vue-pdf 仓库, 修改 pdfjs-dist 版本, 升级到 3.3.122 (我是 vue2 项目 node 10 环境)修改源码中引用地址带有 pdfjs-dist/es5/ 的地方, 去掉 es5 , 另外如果还有报错自己搜一下 pdfjs-dist/ , 看看引用…...

基于moonshot模型的Dify大语言模型应用开发核心场景

基于moonshot模型的Dify大语言模型应用开发核心场景学习总结 一、Dify环境部署 1.Docker环境部署 这里使用vagrant部署&#xff0c;下载vagrant之后&#xff0c;vagrant up登陆&#xff0c;vagrant ssh&#xff0c;在vagrant 中使用 vagrant centos/7 init 快速创建虚拟机 安装…...

华为OD机试真题——字符串序列判定(2025B卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 B卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…...

在Java的list.forEach(即 Stream API 的 forEach 方法)中,无法直接使用 continue 或 break 语句的解决办法

说明 在 Java 的 list.forEach&#xff08;即 Stream API 的 forEach 方法&#xff09;中&#xff0c;无法直接使用 continue 或 break 语句&#xff0c;因为它是一个终结操作&#xff08;Terminal Operation&#xff09;&#xff0c;依赖于 Lambda 表达式或方法引用。 有些时…...

Java面向对象高级学习笔记

面向对象高级 -类变量 类变量-提出问题 提出问题的主要目的就是让大家思考解决之道&#xff0c;从而引出我要讲的知识点 说:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?&#xff0c;编写程序解决。 类变量快速入门 思考: 如果,设计一个int co…...

LLM之Agent:Mem0的简介、安装和使用方法、案例应用之详细攻略

LLM之Agent&#xff1a;Mem0的简介、安装和使用方法、案例应用之详细攻略 目录 Mem0的简介 1、Mem0的特点 2、性能&#xff1a; Mem0的安装及使用方法 1、安装 2、基本用法&#xff08;基本用法&#xff09; Mem0的案例应用 Mem0的简介 Mem0&#xff08;发音为“mem-ze…...

工商总局可视化模版-Echarts的纯HTML源码

概述 基于ECharts的工商总局数据可视化HTML模版&#xff0c;帮助开发者快速搭建专业级工商广告数据展示平台。这款模版设计规范&#xff0c;功能完善&#xff0c;适合各类工商监管场景使用。 主要内容 本套模版采用现代化设计风格&#xff0c;主要包含以下核心功能模块&…...

Spring AI 和 Elasticsearch 作为你的向量数据库

作者&#xff1a;来自 Elastic Josh Long, Philipp Krenn 及 Laura Trotta 使用 Spring AI 和 Elasticsearch 构建一个完整的 AI 应用程序。 Elasticsearch 原生集成了业界领先的生成式 AI 工具和服务提供商。查看我们关于超越 RAG 基础或使用 Elastic 向量数据库构建生产级应用…...

阿里云OSS Api工具类不使用sdk

本文工具实现了OSS简单的上传、下载、获取bucket列表功能&#xff0c;一个工具类搞定&#xff0c;不用集成oss sdk v1签名算法 v1算法&#xff08;v1算法将在2025年9月停用&#xff0c;旧的key不受影响&#xff0c;新key必须用v4&#xff09; v1签名工具类OssV1Signer.java …...

集群聊天服务器学习 配置开发环境(VScode远程连接虚拟机Linux开发)(2)

配置远程开发环境 第一步&#xff1a;Linux系统运行sshd服务 第二步&#xff1a;在vscode上安装Remote Deve I opment插件&#xff0c;其依赖插件会自动安装 第三步&#xff1a;配置远程Linux主机的信息 第四步&#xff1a;在vscode上开发远程连接Linux 第一步&#xff1a;…...

rabbitmq的使用介绍

一.队列工作模式介绍 1.WorkQueues模型 生产者直接把消息发送给队列&#xff0c;然后消费者订阅队列 特点: 消息不会重复, 分配给不同的消费者. 代码实现&#xff1a; 消费者代码&#xff1a; Component Slf4j public class SpringRabbitListener {RabbitListener(queues &q…...

前端的core-js是什么?有什么作用?

core-js 是前端生态中一个重要的 JavaScript 标准库 polyfill&#xff0c;它的主要作用是为不同浏览器环境提供 ECMAScript 最新特性 和 API 的兼容性支持。以下是其核心作用的详细解析&#xff1a; 一、core-js 是什么&#xff1f; 本质&#xff1a;一个模块化的 JavaScript …...

【Python 命名元祖】collections.namedtuple 学习指南

&#x1f4da; collections.namedtuple 学习指南 命名元组&#xff08;namedtuple&#xff09;是 Python collections 模块中一种增强型元组&#xff0c;支持通过字段名访问元素&#xff0c;同时保持元组的内存效率和不可变性。 一、基础用法 1. 定义命名元组 from collectio…...

系统编程day04

一.进程的基本概念 一.定义 进程是一个程序执行的过程&#xff08;也可以说是正在运行的程序&#xff09;&#xff0c;是系统分配资源的基本单位&#xff0c;由cpu对各个进程指挥调度&#xff0c;在单核cpu的情况下,各个进程可以通过一定规则在cpu上并发运行。 二.PCB块 1.PC…...

java 加密算法的简单使用

简介 加密算法&#xff0c;就是将原本的明文&#xff0c;通过一系列操作变成密文。在这里介绍一些常用的加密算法。在日常开发中&#xff0c;接触到了一些加密算法&#xff0c;例如&#xff0c;用户的隐私信息&#xff0c;诸如密码、手机号等&#xff0c;需要加密后存储到数据…...

Arduino Uno KY-037声音传感器实验

KY-037声音传感器实验 KY-037声音传感器实验1、 实验内容2、KY-037声音传感器介绍3、实验注意事项4、代码和实验现象 KY-037声音传感器实验 1、 实验内容 通过对KY-037声音传感器吹气&#xff0c;控制LED的打开和关闭&#xff0c;吹一下LED打开&#xff0c;在吹一下LED关闭。…...

机器学习---各算法比较

机器学习算法 线性回归 优点&#xff1a;简单&#xff1b;适用于大规模数据集。 缺点&#xff1a;无法处理非线性关系&#xff1b;对异常值敏感。 多项式回归 优点&#xff1a;捕捉特征和目标之间的非线性关系。 缺点&#xff1a;可能会过度拟合数据。 岭回归 优点&#…...

基于音频Transformer与动作单元的多模态情绪识别算法设计与实现(在RAVDESS数据集上的应用)

摘要&#xff1a;情感识别技术在医学、自动驾驶等多个领域的广泛应用&#xff0c;正吸引着研究界的持续关注。本研究提出了一种融合语音情感识别&#xff08;SER&#xff09;与面部情感识别&#xff08;FER&#xff09;的自动情绪识别系统。在SER方面&#xff0c;我们采用两种迁…...

Flink SQL 计算实时指标同比的实现方法

在 Flink SQL 中计算实时指标的同比(Year-on-Year),核心是通过时间窗口划分周期(如日、月、周),并关联当前周期与去年同期的指标值。以下是结合流数据处理特性的具体实现方法,包含数据准备、窗口聚合、历史数据关联等关键步骤。 一、同比的定义与场景 同比指当前周期指…...

什么是VR实景?有哪些高价值场景?

在数字化浪潮的推动下&#xff0c;虚拟现实技术正以前所未有的速度改变着我们的生活方式和工作模式。 其中&#xff0c;VR实景作为VR技术的一个重要应用场景&#xff0c;独特的沉浸感和交互性&#xff0c;在众多领域展现出应用潜力和高价值场景。什么是VR实景&#xff1f;VR实…...

基于MATLAB实现传统谱减法以及两种改进的谱减法(增益函数谱减法、多带谱减法)的语音增强

基于MATLAB实现传统谱减法以及两种改进的谱减法&#xff08;增益函数谱减法、多带谱减法&#xff09;的语音增强代码示例&#xff1a; 传统谱减法 function enhanced traditional_spectral_subtraction(noisy, fs, wlen, inc, NIS, a, b)% 参数说明&#xff1a;% noisy - 带…...

同一无线网络下的设备IP地址是否相同?

在家庭和办公网络普及的今天&#xff0c;许多人都会好奇&#xff1a;连接同一个Wi-Fi的设备是否共享相同的IP地址&#xff1f;这个问题看似简单&#xff0c;实则涉及多个角度。本文将为您揭示其中的技术奥秘。 用一个无线网IP地址一样吗&#xff1f;同一无线网络&#xff08;如…...

第2周 PINN核心技术揭秘: 如何用神经网络求解偏微分方程

1. PDEs与传统数值方法回顾 (Review of PDEs & Traditional Numerical Methods) 1.1 什么是偏微分方程 (Partial Differential Equations, PDEs)? 偏微分方程是描述自然界和工程领域中各种物理现象(如热量传播、流体流动、波的振动、电磁场分布等)的基本数学语言。 1.…...

【C语言】习题练手套餐 2

每日习题分享。 字符串函数的运用 首先回顾一下字符串函数。 字符串长度 strlen(const char *s);功能&#xff1a;计算字符串的长度&#xff0c;不包含终止符\0。 字符串连接 char *strcat(char *dest, const char *src); char *strncat(char *dest, const char *src, si…...

[项目总结] 基于Docker与Nginx对项目进行部署

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…...

神经正切核推导(2)

对神经正切核的理解和推导&#xff08;1&#xff09;-CSDN博客 这篇文章包括很多概念的理解 声明&#xff1a; 本篇文章来自于Neural Tangent Kernel &#xff08;NTK&#xff09;基础推导 - Gearlesskai - 博客园 旨在对上述推导过程进行再推导与理解 手写推导部分与其他颜…...

Python模型优化技巧

在机器学习与数据分析领域&#xff0c;模型优化是提升预测准确性、缩短训练时间、降低资源消耗的核心环节。本文结合实战经验&#xff0c;从数据预处理、特征工程、模型调优、代码优化到部署监控&#xff0c;系统梳理Python模型优化的关键技巧&#xff0c;助你打造高效能模型。…...

Redis 面试场景

文章目录 项目地址一、Redis使用场景1.1 统计网站访问次数1.2 产品分类树1.3 分布式锁(常见)1.4 排行榜1.5 记录用户登录状态(记录)1.6 限流1.7 缓存加速1.8消息队列1.9 全局ID生成1.10 订餐系统场景1 . 单体版2. 故事板二、OutBox Pattern2.1 项目3. Saga状态机4. 日志4. …...

MySQL 索引失效及其解决办法

一、前言 在数据库优化中,索引(Index)是一项至关重要的技术手段,可以显著提升查询性能。然而,在实际开发过程中,MySQL 索引并不总是如预期生效。本文将从原理出发,系统地介绍索引失效的常见场景及其解决方案,帮助开发者有效规避性能陷阱。 二、索引基础回顾 MySQL 支…...