JDBC连接池多线程通过CountDownLatch实现线程并发执行

news2025/1/15 16:44:09

目录

1.连接池

1.1 什么是连接池

1.2 为什么使用连接池

1.3 连接池的工作原理

1.4我们如何手写【难点】

1.4.1编写说明

1.4.2 创建jdbc.properties

1.4.3 封装一个连接类

1.4.4 创建一个连接池接口

1.4.5 创建一个连接池实现类以及对应方法

1.4.6 创建一个连接池的维护类

1.4.7 案例测试

 1.5 市面上有哪些可用的连接池


1.连接池

1.1 什么是连接池

连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。

连接池是装有连接的容器,使用连接的话,可以从连接池中进行获取,使用完成之后将连接归还给连接池。

1.2 为什么使用连接池

连接对象创建和销毁是需要耗费时间的,在服务器初始化的时候就初始化一些连接。把这些连接放入到内存中,使用的时候可以从内存中获取,使用完成之后将连接放入连接池中。从内存中获取和归还的效率要远远高于创建和销毁的效率。(提升性能)。

1.3 连接池的工作原理

1.4我们如何手写【难点】

1.4.0 准备工作数据库的搭建

/*
 Navicat Premium Data Transfer

 Source Server         : MySQL80_3306
 Source Server Type    : MySQL
 Source Server Version : 80030
 Source Host           : localhost:3306
 Source Schema         : powernode_jdbc

 Target Server Type    : MySQL
 Target Server Version : 80030
 File Encoding         : 65001

 Date: 25/01/2023 20:48:08
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

drop database if exits powernode_jdbc;
CREATE database powernode_jdbc;
use powernode_jdbc;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `age` int NULL DEFAULT NULL,
  `sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (6, '小明3', 4, '女', '武汉3');
INSERT INTO `student` VALUES (7, '小明4', 51, '女', '武汉4');
INSERT INTO `student` VALUES (8, '小明5', 39, '女', '武汉5');
INSERT INTO `student` VALUES (9, '小明6', 38, '女', '武汉6');
INSERT INTO `student` VALUES (10, '小明7', 67, '男', '武汉7');
INSERT INTO `student` VALUES (11, '小明8', 73, '女', '武汉8');
INSERT INTO `student` VALUES (12, '小明9', 64, '男', '武汉9');
INSERT INTO `student` VALUES (13, '小明10', 74, '男', '武汉10');
INSERT INTO `student` VALUES (14, '小明11', 56, '男', '武汉11');
INSERT INTO `student` VALUES (15, '小明12', 50, '男', '武汉12');
INSERT INTO `student` VALUES (16, '小明13', 68, '男', '武汉13');
INSERT INTO `student` VALUES (17, '小明14', 47, '男', '武汉14');
INSERT INTO `student` VALUES (18, '小明15', 96, '女', '武汉15');
INSERT INTO `student` VALUES (19, '小明16', 86, '女', '武汉16');
INSERT INTO `student` VALUES (20, '小明17', 73, '女', '武汉17');
INSERT INTO `student` VALUES (21, '小明18', 31, '女', '武汉18');
INSERT INTO `student` VALUES (22, '小明19', 61, '女', '武汉19');
INSERT INTO `student` VALUES (23, '小明20', 30, '女', '武汉20');

SET FOREIGN_KEY_CHECKS = 1;

1.4.1编写说明

我们现在编写的时候一个使用连接池,一个不使用连接池,到时候测试的时候做下时间对比

1.4.2 创建jdbc.properties

jdbc.properties放在resources目录下

对应的数据库的各种信息,以及数据库连接池初始化时数量最大连接数量以及自动增长数量

参数说明
initConnectCount数据库连接池 初始化时数量
maxConnects数据库连接池 最大连接数量
incrementCount数据库连接池 自动增长数量
jdbcDriver = com.mysql.cj.jdbc.Driver
jdbcUrl = jdbc:mysql://localhost:3306/powernode_jdbc?&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username = root
password = root

initConnectCount = 20
maxConnects = 100
incrementCount = 3

1.4.3 封装一个连接类

该类中包含一个数据库连接,一个是否使用的标记以及一个close方法,用来将该连接置为可用状态,从而达到数据库连接的可复用,减少持续创建新连接的资源消耗

package com.bjpowernode.jdbc._08连接池.domain;

import java.sql.Connection;

public class PoolConnection {
    /**
     * 数据库连接
     */
    private Connection conn = null;

    /**
     * 标记该连接是否使用
     */
    private boolean isUse = false;

    /**
     * 构造方法
     * @param conn  连接对象
     * @param isUse  是否正在使用 【模拟关闭】
     */
    public PoolConnection(Connection conn, boolean isUse) {
        this.conn = conn;
        this.isUse = isUse;
    }

    public Connection getConn() {
        return conn;
    }

    public void setConn(Connection conn) {
        this.conn = conn;
    }

    public boolean isUse() {
        return isUse;
    }

    public void setUse(boolean use) {
        isUse = use;
    }

    /**
     * 将该连接置为可用状态
     */
    public void close() {
        this.isUse = false;
    }
}

1.4.4 创建一个连接池接口

对外提供的连接池的接口

package com.bjpowernode.jdbc._08连接池.service;

import com.bjpowernode.jdbc._08连接池.domain.PoolConnection;

import java.sql.Connection;

public interface IPool {
    /**
     * 获取连接池中可用连接
     */
    PoolConnection getConnection();

    /**
     * 获取一个数据库连接(不使用连接池)
     */
    Connection getConnectionNoPool();
}

1.4.5 创建一个连接池实现类以及对应方法

首先加载对应配置文件中信息,初始化数据库连接池,然后用synchronized来实现多线程情况下线程安全的获取可用连接

package com.bjpowernode.jdbc._08连接池.service.impl;

import com.bjpowernode.jdbc._08连接池.domain.PoolConnection;
import com.bjpowernode.jdbc._08连接池.service.IPool;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Vector;

public class JdbcPool implements IPool {
    //驱动
    private static String jdbcDriver;
    //连接地址
    private static String jdbcUrl;
    //数据库用户名
    private static String username;
    //数据库密码
    private static String password;
    //初始化连接数
    private static Integer initConnectCount;
    //最大连接数
    private static Integer maxConnects;
    //当连接不够时自动增长的数
    private static Integer incrementCount;
    //因为Vector是线程安全的,所有暂时选择它
    private static Vector<PoolConnection> connections = new Vector<>();

    /*
     * 通过实例初始化块来初始化
     */
    {
        //读取对应的配置文件,加载入properties中,并设置到对应的参数中
        InputStream is = JdbcPool.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        try {
            properties.load(is);
        } catch (IOException e)  {
            e.printStackTrace();
        }
        jdbcDriver = properties.getProperty("jdbcDriver");
        jdbcUrl = properties.getProperty("jdbcUrl");
        username = properties.getProperty("username");
        password = properties.getProperty("password");
        initConnectCount = Integer.valueOf(properties.getProperty("initConnectCount"));
        maxConnects = Integer.valueOf(properties.getProperty("maxConnects"));
        incrementCount = Integer.valueOf(properties.getProperty("incrementCount"));

        try {
            /*
             * 注册jdbc驱动
             * */
            Driver driver = (Driver) Class.forName(jdbcDriver).newInstance();
            DriverManager.registerDriver(driver);
            /*
             * 根据initConnectCount来初始化连接池
             * */
            createConnections(initConnectCount);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取可用连接
     */
    @Override
    public PoolConnection getConnection() {
        if (connections.isEmpty()) {
            System.out.println("连接池中没有连接");
            throw new RuntimeException("连接池中没有连接");
        }

        return getActiveConnection();
    }
    /**
     * 同步方法来获取连接池中可用连接,在多线程情况下,只有一个线程访问该方法来获取连接,防止由于多线程情况下多个线程获取同一个连接从而引起出错
     */
    private synchronized PoolConnection getActiveConnection() {
        /*
         * 通过循环来获取可用连接,若获取不到可用连接,则依靠无限循环来继续获取
         * */
        while (true) {
            for (PoolConnection con : connections) {
                if (!con.isUse()) {
                    Connection trueConn = con.getConn();
                    try {
                        //验证连接是否失效 0表示不校验超时
                        if (!trueConn.isValid(0)) {
                            con.setConn(DriverManager.getConnection(jdbcUrl, username, password));
                        }
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                    con.setUse(true);
                    return con;
                }
            }
            /*
             * 根据连接池中连接数量从而判断是否增加对应的数量的连接
             * */
            if (connections.size() <= maxConnects - incrementCount) {
                createConnections(incrementCount);
            } else if (connections.size() < maxConnects && connections.size() > maxConnects - incrementCount) {
                createConnections(maxConnects - connections.size());
            }
        }
    }

    /*
     * 创建对应数量的连接并放入连接池中
     * */
    private void createConnections(int count) {
        for (int i = 0; i < count; i++) {
            if (maxConnects > 0 && connections.size() >= maxConnects) {
                System.out.println("连接池中连接数量已经达到最大值");
                throw new RuntimeException("连接池中连接数量已经达到最大值");
            }
            try {
                Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
                /*
                 * 将连接放入连接池中,并将状态设为可用
                 * */
                connections.add(new PoolConnection(connection, false));

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /*
     * 获取连接池中连接数量
     * */
    public int getSize() {
        return connections.size();
    }
    @Override
    public Connection getConnectionNoPool() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(jdbcUrl, username, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }
}

1.4.6 创建一个连接池的维护类

通过静态内部类来实现连接池的单例模式

package com.bjpowernode.jdbc._08连接池;

import com.bjpowernode.jdbc._08连接池.service.impl.JdbcPool;

public class PoolManager {
    /**
     * 静态内部类实现连接池的单例
     * */
    private static class CreatePool{
        private static JdbcPool pool = new JdbcPool();
    }

    public static JdbcPool getInstance(){
        return CreatePool.pool;
    }
}

1.4.7 案例测试

        最后,上测试方法:起2000个线程,通过new两个CountDownLatch,其中一个来实现线程的同时并发执行,

  熟话说,没有对比就没有伤害,我们先来用普通没有连接池的测试一下2000个连接并行执行的时间,代码如下:

package com.bjpowernode.jdbc._08连接池.test;

import com.bjpowernode.jdbc._08连接池.PoolManager;
import com.bjpowernode.jdbc._08连接池.service.impl.JdbcPool;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CountDownLatch;


public class JdbcNoPoolMain {
    static final int threadSize = 2000;

    static JdbcPool jdbcPool = PoolManager.getInstance();

    static CountDownLatch countDownLatch1 = new CountDownLatch(1);
    static CountDownLatch countDownLatch2 = new CountDownLatch(threadSize);

    public static void main(String[] args) throws InterruptedException {
        threadTest();
    }

    public static void threadTest() throws InterruptedException {
        long time1 = System.currentTimeMillis();
        for (int i = 0; i < threadSize; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //使得线程阻塞到coutDownLatch1为0时才执行
                        countDownLatch1.await();
                        selectNoPool();
                        //每个独立子线程执行完后,countDownLatch2减1
                        countDownLatch2.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        //将countDownLatch1置为0,从而使线程并发执行
        countDownLatch1.countDown();
        //等待countDownLatch2变为0时才继续执行
        countDownLatch2.await();
        long time2 = System.currentTimeMillis();
        System.out.println("thread size: " + threadSize + " no use pool :" + (time2 - time1));
    }

    public static void selectNoPool() throws SQLException {

        Connection conn = jdbcPool.getConnectionNoPool();
        Statement sm = null;
        ResultSet rs = null;
        try {
            sm = conn.createStatement();
            rs = sm.executeQuery("select * from student");
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            while (rs.next()) {
                System.out.println(Thread.currentThread().getName() + " ==== " + "name: " + rs.getString("name") + " age: " + rs.getInt("age"));
            }
            Thread.sleep(100);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        rs.close();
        sm.close();
        conn.close();
    }
}

 1.5 市面上有哪些可用的连接池

  • c3p0  老了
  • druid 阿里的
  • dbcp 老了
  • Hikari  小日本的,springboot官方推荐的

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

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

相关文章

spingboot如何接受前端请求

首先我们是否用的是rest风格开发的的都是适用的.普通参数get 请求发送方注:由于是get请求不用body(json)接收.接受方post请求发送端注意:在请求体(body)里面用x-www-from-urlencoded(不仅可以发请求,还可以发文件)接受体没有5种不同参数类型的传递普通参数[简单数据]&#xff1…

用 Java 实现计算器功能

练习一 1.设计一个类模拟一个计算器 达到什么需求&#xff1f;加减乘除 需要设计一个方法一个计算方法 控制台输出 首先请输入第一个数 例如数字 1 请输入符号 例如 请输入第二个数 例如 2 第二次 数字 3 请输入符号 - 请输入第二个数 2 结果 1 程序解析&#…

27. 作用域

1. 定义 作用域就是一个 python 程序可以直接访问命名空间的正文区域。 在一个 python 程序中&#xff0c;直接访问一个变量&#xff0c;会从内到外依次访问所有的作用域直到找到&#xff0c;否则会报未定义的错误。 python 中&#xff0c;程序的变量并不是在哪个位置都可以访…

【Hadoop】MapReduce原理剖析(Map,Shuffle,Reduce三阶段)

文章目录1. Map阶段1.1 把输入文件(夹)划分为很多InputSplit(Split)1.2 分配并执行map作业2. Shuffle阶段2.1 Partition(分区)2.2 Sort(排序)2.3 Group(分组)2.4 Combiner(规约)2.5 序列化并写入Linux磁盘内存2.6 反序列化读取数据到不同的reduce节点2.7 Reduce端数据进行合并、…

【数据库概论】第五章 数据库完整性

第五章 数据库完整性 目录第五章 数据库完整性5.1 实体完整性5.2 参照实体性5.3 用户定义的完整性1.属性上的约束条件2.元组上的约束条件5.4 完整性约束命名子句5.5 域中的完整性限制5.6 断言5.7 触发器(Trigger)一、定义触发器二、激活触发器三、删除触发器数据库的完整性指的…

好客租房-13.WebMagic

13. 项目接入ES编写爬虫抓取房源数据开发搜索房源接口服务整合前端开发实现搜索功能优化搜索功能增加高亮和分页功能热词推荐功能实现拼音分词13.1 制作假数据13.1.1 WebMagic抓取数据为了丰富我们的房源数据&#xff0c;所以我们采用WebMagic来抓取一些数据&#xff0c;目标网…

还在纠结选择用什么浏览器?手机端用国产浏览器也很香

一说到受欢迎的电脑浏览器&#xff0c;大家肯定不约而同地说谷歌浏览器。微软edge浏览器能够同步书签、插件也非常多&#xff0c;因为这些优势深受国人的喜爱。有人纠结在国内选择谷歌好&#xff0c;还是edge浏览器好呢&#xff1f;可能有的人哪个也不选&#xff0c;反而在电脑…

Docker 解决 `denied: requested access to the resource is denied`

背景 由于不可描述的原因&#xff0c;相对于以前&#xff0c;最近在更加频繁的迁移服务器&#xff0c;简单的 Shell 脚本已经不能满足需求了&#xff0c;于是将所有的项目 Docker 化。 部分不含敏感配置的项目准备放到 DockerHub 上面&#xff0c;但是在 docker push 的时候报…

利用 Algolia 为静态博客搭建实现内容搜索

现在静态博客的标配之一就是博客搜索&#x1f50d;&#xff0c;我也是通过搭建博客发现了它&#xff0c;这篇主要记录一下怎么使用 algolia 完成博客搜索&#xff0c;自己的博客搭建使用的是 docusaurus 。 注册账号 首先需要去 algolia 官网注册自己的账号&#xff0c;可以直…

Java线程池(超详细)

1、基本概念 Java线程需要经过线程的创建&#xff0c;调用和销毁整个过程&#xff0c;频繁的创建和销毁会大大影响性能&#xff0c;所以引入的线程池&#xff1a; 好处&#xff1a; 提升性能&#xff1a;线程池能独立负责线程的创建、维护和分配线程管理&#xff1a;每个Java…

k8s安装kuboard面板

前面介绍了k8s的dashboard面板&#xff0c;这里介绍国人开发的kuboard面板&#xff0c;相较于dashboard面板&#xff0c;kuboard面板对很多运维调试功能做了很多增强。官方文档&#xff1a;https://www.kuboard.cn/install/v3/install.html#kuboard-v3-x-%E7%89%88%E6%9C%AC%E8…

实现一个TCP客户端——服务端协议

目录 TCP客户端常见的API&#xff1a; ServerSocket: Socket&#xff1a; TCP服务端(单线程版本) 属性构造方法: 启动服务端的start()方法 步骤一&#xff1a;接收客户端发送的socket 步骤二&#xff1a; 调用processConnection方法来处理客户端发送的连接 ①通过参数传入的…

影像组学|特征定义以及提取

一、 影像组学特征分类 1.1 影像组学特征分类 1.1.1 一阶统计特征 一阶统计特征&#xff0c;反应所测体素的对称性、均匀性以及局部强度分布变化。包括中值&#xff0c;平均值&#xff0c;最小值&#xff0c;最大值&#xff0c;标准差&#xff0c;偏度&#xff0c;峰度等。 …

【Linux】六、Linux 基础IO(三)|文件系统|软硬链接|文件的三个时间

目录 八、文件系统 8.1 磁盘 8.1.1 磁盘的物理结构 8.1.2 磁盘的存储结构 8.1.3 磁盘的逻辑结构 8.2 inode 九、软硬链接 9.1 软链接 9.2 硬链接 9.3 当前路径(.)和上级路径(..) 十、文件的三个时间 八、文件系统 上面的内容谈论的都是一个被打开文件&#xff0c;那…

如何将两个录音合成一个?这篇文章告诉你

现如今&#xff0c;很多小伙伴都加入到短视频行业当中。而短视频的制作往往需要将多段音频进行一个合并。那么问题来了&#xff0c;当你想多个音频进行合并在一起的时候&#xff0c;你是怎么做的呢&#xff1f;其实很简单&#xff0c;我们只需要借助市面上的一些合并软件就好了…

初始网络

文章目录初始网络局域网 / 广域网IP地址 和 端口号认识协议协议分层初始网络 这里可以先自行在网上了解一下网络的发展史 也就是互联网是怎么来的. 局域网 / 广域网 关于网络的发展史 , 会涉及到两个非常重要的术语 &#xff0c;也就是 局域网&#xff0c;和广域网 。 局域网 &…

JavaEE多线程-阻塞队列

目录一、认识阻塞队列1.1 什么是阻塞队列&#xff1f;1.2 生产者消费者模型1.3 标准库中的阻塞队列类二、循环队列实现简单阻塞队列2.1 实现循环队列2.2 阻塞队列实现一、认识阻塞队列 1.1 什么是阻塞队列&#xff1f; 阻塞队列&#xff1a;从名字可以看出&#xff0c;他也是…

简明Java讲义 2:数据类型和运算符

目录 1、安装IDE编辑器 2、关键字和保留字 3、标识符 4、分隔符 5、数据类型 6、基本类型的数据类型转换 7、表达式类型的自动提升 8、变量 9、运算符 10、运算符的优先级 1、安装IDE编辑器 在开始内容之前&#xff0c;先下载IDE&#xff0c;可以是Eclipse或STS&…

Python函数(函数定义、函数调用)用法详解

Python 中函数的应用非常广泛&#xff0c;前面章节中我们已经接触过多个函数&#xff0c;比如 input() 、print()、range()、len() 函数等等&#xff0c;这些都是 Python 的内置函数&#xff0c;可以直接使用。除了可以直接使用的内置函数外&#xff0c;Python 还支持自定义函数…

LeetCode刷题模版:201 - 210

目录 简介201. 数字范围按位与202. 快乐数203. 移除链表元素204. 计数质数205. 同构字符串206. 反转链表207. 课程表【未实现】208. 实现 Trie (前缀树)209. 长度最小的子数组210. 课程表 II【未实现】结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您…