连接池中的连接可重复使用,减少每次新建和烧毁连接对资源的消耗,但连接池的容量大小也要设置合理,否则也会占用多余的资源。连接池的基本功能是获取连接和释放连接

连接在java中也是一个类,连接对象是一个普通java对象,连接池也是如此,本例使用Connection代表连接类,ConnectionPool代表连接池,主要用到的技术为wait/notify机制以及CountDownLatch的使用

第一版的代码

日志使用的为log4j2,配置内容直接拷贝官网内容,将日志级别改为了info,并去掉了一些不必要的打印

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="Info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

连接池ConnectionPool

package com.demo.Multithreading.ConnectionPool;

import java.util.LinkedList;

public class ConnectionPool {
// 存放连接
private LinkedList<Connection> pool = new LinkedList<Connection>();
// 连接池的初始容量
private int capacity; public ConnectionPool() {
capacity = 10;
init();
} public ConnectionPool(int capacity) {
this.capacity = capacity;
init();
} /**
* 初始化连接池
*/
public void init() {
for (int i = 0; i < capacity; i++) {
pool.add(Connection.createConnection(i));
}
} /**
* 获取连接,指定的时间内未获取到,则返回null
*
* @param timeout
* 等待超时时间
* @return
* @throws InterruptedException
*/
public Connection getConnection(long timeout) throws InterruptedException {
synchronized (pool) {
if (pool.size() == 0) {
// 连接池为空,在指定时间内等待以获取连接
pool.wait(timeout);
// 收到其他线程notify,准备获取连接
if (pool.size() > 0) {
// 连接池中存在连接可获取
Connection removeFirst = pool.removeFirst();
return removeFirst;
}
// 未获取到连接,返回null
return null;
} else {
// 连接池中存在连接,直接获取
Connection removeFirst = pool.removeFirst();
return removeFirst;
}
}
} /**
* 释放连接
*
* @param con
*/
public void releaseConnection(Connection con) {
synchronized (pool) {
if (con != null) {
// 在末尾添加连接
pool.addLast(con);
// 通知其他线程获取连接
pool.notifyAll();
}
}
}
}

连接Connection

package com.demo.Multithreading.ConnectionPool;

public class Connection {

    private int num;

    private Connection(int num) {
this.num = num;
} public static Connection createConnection(int num) {
return new Connection(num);
} @Override
public String toString() {
return "Connection [num=" + num + "]";
} }

测试时线程类MyThread

package com.demo.Multithreading.ConnectionPool.test;

import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.demo.Multithreading.ConnectionPool.Connection;
import com.demo.Multithreading.ConnectionPool.ConnectionPool; public class MyThread extends Thread {
private Logger logger = LoggerFactory.getLogger(MyThread.class);
private ConnectionPool pool;
private CountDownLatch latch; public MyThread(String name, CountDownLatch latch, ConnectionPool pool) {
this.pool = pool;
this.latch = latch;
this.setName(name);
} @Override
public void run() {
Connection connection = null;
try {
connection = pool.getConnection(5000);
Thread.sleep(2000);
logger.info(Thread.currentThread().getName() + ": " + connection);
} catch (InterruptedException e) {
logger.error("", e);
} latch.countDown();
} }

测试类App

package com.demo.Multithreading;

import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.demo.Multithreading.ConnectionPool.ConnectionPool;
import com.demo.Multithreading.ConnectionPool.test.MyThread; public class App {
private static Logger logger = LoggerFactory.getLogger(App.class); public static void main(String[] args) {
ConnectionPool cp = new ConnectionPool(3);
int threadCount = 5;
CountDownLatch cd = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
MyThread th = new MyThread("mythread" + i, cd, cp);
th.start();
} try {
cd.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("execute end.");
}
}

执行测试类中main方法,输出结果如下

11:23:45.307 [mythread0] mythread0: Connection [num=0]
11:23:45.307 [mythread2] mythread2: Connection [num=2]
11:23:45.307 [mythread1] mythread1: Connection [num=1]
11:23:50.293 [mythread3] mythread3: null
11:23:50.294 [mythread4] mythread4: null
11:23:50.294 [main] execute end.

连接池中初始化3个连接,5个线程同时获取连接,有两个连接未获取到连接池,在MyThread中获取连接的最大时间为5秒,线程休眠2秒,可认为线程执行时间为2秒,既然如此,最开始获取到连接的三个线程在2秒左右,不超过3秒的时间内一定能执行结束,另外两条线程也能获取到连接,但真实输出结果是,后面两个线程未获取到连接,究其原因,还是因为没有释放连接。对于可复用资源,比如连接池,线程池等,也包括java io流,在使用之后一定要记得关闭。

修改测试线程MyThread中run方法,释放连接

package com.demo.Multithreading.ConnectionPool.test;

import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.demo.Multithreading.ConnectionPool.Connection;
import com.demo.Multithreading.ConnectionPool.ConnectionPool; public class MyThread extends Thread {
private Logger logger = LoggerFactory.getLogger(MyThread.class);
private ConnectionPool pool;
private CountDownLatch latch; public MyThread(String name, CountDownLatch latch, ConnectionPool pool) {
this.pool = pool;
this.latch = latch;
this.setName(name);
} @Override
public void run() {
Connection connection = null;
try {
connection = pool.getConnection(5000);
Thread.sleep(2000);
logger.info(Thread.currentThread().getName() + ": " + connection);
} catch (InterruptedException e) {
logger.error("", e);
} finally {
if (connection != null) {
pool.releaseConnection(connection);
}
} latch.countDown();
} }

执行结果

11:39:53.906 [mythread1] mythread1: Connection [num=1]
11:39:53.906 [mythread0] mythread0: Connection [num=0]
11:39:53.906 [mythread2] mythread2: Connection [num=2]
11:39:55.914 [mythread3] mythread3: null
11:39:55.914 [mythread4] mythread4: Connection [num=1]
11:39:55.915 [main] execute end.

还是有线程未获取到连接,

分析:五条线程同时执行,前三条线程获取到锁,线程sleep 2s,另外两条线程未获取到锁,处于timed waiting,当前三条线程中有一条线程执行结束释放连接,同时notifyAll,则处于timed waiting状态的两条线程同时变为runnable状态,继续执行,此时只有一个连接,其中一个线程获取后,另外一个线程获取到的就为null,所以出现上述情况,但这种情况不是必现的,有时五条线程都可以获取到连接,该方式不稳定。

改进:notifyAll是唤醒所有在当前锁上等待的线程,notify是唤醒任意一个在当前锁上等待的线程,那如果将notifyAll改为notify,每次释放连接时,只唤醒一条线程,这种思路理论上是可以的我觉得

对App测试类进行改进,循环20次进行输出打印

package com.demo.Multithreading;

import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.demo.Multithreading.ConnectionPool.ConnectionPool;
import com.demo.Multithreading.ConnectionPool.test.MyThread; public class App {
private static Logger logger = LoggerFactory.getLogger(App.class); public static void main(String[] args) { for(int j=0;j<20;j++) {
ConnectionPool cp = new ConnectionPool(3);
int threadCount = 5;
CountDownLatch cd = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
MyThread th = new MyThread("mythread" + i, cd, cp);
th.start();
} try {
cd.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("execute end.");
} }
}

分别使用notifyAll和notify方法唤醒线程

结果:

notifyAll方法,有14次只有四条线程获取到连接,6次五条线程都获取到连接

notify方法,20次五条线程均获取到连接

当然,notify和notifyAll方法要看使用场景,在本例中使用notify方法比使用notifyAll方法合适

如果使用notifyAll,还会有一个比较严重的问题,就是超时时间不准确,还是按照3个连接,5个线程来分析,前三个执行结束,后两个被notifyAll唤醒,当其中一个线程获取到连接后,假设另外一个线程未获取到连接,则此时该线程返回null是不准确的,因为此时很可能还没有到超时时间。

第二版代码

在第二版中主要对连接池获取连接的方法做了处理,使其无论是使用notify还是notifyAll都可以正常,并且支持无限等待

package com.demo.Multithreading.ConnectionPool;

import java.util.LinkedList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; public class ConnectionPool {
private Logger logger = LoggerFactory.getLogger(ConnectionPool.class);
// 存放连接
private LinkedList<Connection> pool = new LinkedList<Connection>();
// 连接池的初始容量
private int capacity; public ConnectionPool() {
capacity = 10;
init();
} public ConnectionPool(int capacity) {
this.capacity = capacity;
init();
} /**
* 初始化连接池
*/
public void init() {
for (int i = 0; i < capacity; i++) {
pool.add(Connection.createConnection(i));
}
} /**
* 获取连接,指定的时间内未获取到,则返回null
*
* @param timeout
* 等待超时时间
* @return
* @throws InterruptedException
*/
public Connection getConnection(long timeout) throws InterruptedException {
synchronized (pool) {
if (timeout <= 0) {
// 无限等待,直到被唤醒
while (pool.isEmpty()) {
pool.wait();
}
return pool.removeFirst();
} else {
// 获取连接超时时的时间点
long futureTime = System.currentTimeMillis() + timeout;
// 获取连接池剩余等待时间
long remain = timeout;
// 连接池为空,并且还有剩余等待时间,则继续等待,并且更新剩余等待时间
while (pool.isEmpty() && remain > 0) {
pool.wait(remain);
remain = futureTime - System.currentTimeMillis();
} if (pool.isEmpty()) {
logger.error("timeout");
} else {
return pool.removeFirst();
} }
} return null;
} /**
* 释放连接
*
* @param con
*/
public void releaseConnection(Connection con) {
synchronized (pool) {
if (con != null) {
// 在末尾添加连接
pool.addLast(con);
// 通知其他线程获取连接
pool.notify();
}
}
}
}

无论使用notify还是notifyAll,在20次测试中,五条线程均可以正常获取到连接,且超时时间是准确的,不会因为被notifyAll唤醒,还未到超时间就返回null

压力测试(本例中线程sleep 2s,可认连接从获取到释放大概为2s时间)

package com.demo.Multithreading;

import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.demo.Multithreading.ConnectionPool.ConnectionPool;
import com.demo.Multithreading.ConnectionPool.test.MyThread; public class App {
private static Logger logger = LoggerFactory.getLogger(App.class); public static void main(String[] args) {
int poolSize = 100;
int threadCount = 500;
ConnectionPool cp = new ConnectionPool(poolSize);
CountDownLatch cd = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
MyThread th = new MyThread("mythread" + i, cd, cp);
th.start();
} try {
cd.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("execute end.");
} }

连接池中100个连接,500条线程,每条线程sleep 2s,连接获取超时时间为5s,未获取到连接的线程数有200个

连接池中100个连接,300条线程,每条线程sleep 2s,连接获取超时时间为5s,全部都获取到连接

连接池中100个连接,500条线程,每条线程sleep 2s,连接获取超时时间为10s,全部都获取到连接

连接池中200个连接,500条线程,每条线程sleep 2s,连接获取超时时间为5s,全部都获取到连接

连接池中100个连接,500条线程,每条线程sleep 1s,连接获取超时时间为5s,全部都获取到连接

通过以上简单测试可以得出,连接池大小,线程数(并发数),获取到连接到释放连接所需时间,连接超时时间都会影响到连接池的获取

对于数据库,设置线程池大小为最大并发数大小不会出现获取不到连接,但会出现连接浪费,应该综合考虑设置参数,比如最大并发数为100,获取到连接到释放连接时间为1s,链接获取超时时间为10s,则此时连接池大小设置为10就差不多可以满足应用所需,但设置的时候可以稍微设置的大一些,比如15

wait/notify模拟连接池的更多相关文章

  1. wait/notify模拟线程池

    线程创建和销毁会消耗很多的资源,当我们创建线程时,会发现cpu利用率很高,为了节省资源的使用,使用线程池是一个比较好的选择,当有任务需要执行时,随机分配给一条线程去执行,也可以删除任务,获取任务数量等 ...

  2. php 如何实现 数据库 连接池

    php 如何实现 数据库 连接池 一.总结 一句话总结: php+sqlrelay+mysql实现连接池及读写负载均衡 master-slave模式增加并发. sqlrelay 解决连接池问题以及实现 ...

  3. MySQL_(Java)【连接池】使用DBCP简单模拟银行转账事物

    dbcp下载 传送门 Commons Pool下载 传送门 Commons log下载 传送门 MySQL_(Java)[事物操作]使用JDBC模拟银行转账向数据库发起修改请求 传送门 MySQL_( ...

  4. java mysql多次事务 模拟依据汇率转账,并存储转账信息 分层完成 dao层 service 层 client层 连接池使用C3p0 写入库使用DBUtils

    Jar包使用,及层的划分 c3p0-config.xml <?xml version="1.0" encoding="UTF-8"?> <c3 ...

  5. 连接SQLServer时,因启用连接池导致孤立事务的原因分析和解决办法

    本文出处:http://www.cnblogs.com/wy123/p/6110349.html 之前遇到过这么一种情况: 连接数据库的部分Session会出现不定时的阻塞,这种阻塞时长时短,有时候持 ...

  6. rpc框架之 thrift连接池实现

    接前一篇rpc框架之HA/负载均衡构架设计 继续,写了一个简单的thrift 连接池: 先做点准备工作: package yjmyzz; public class ServerInfo { publi ...

  7. java的几种连接池

    连接池的管理用了了享元模式,这里对连接池进行简单设计. 一.设计思路 1.连接池配置属性DBbean:里面存放可以配置的一些属性 2.连接池接口IConnectionPool:里面定义一些基本的获取连 ...

  8. DBCP、C3P0、Proxool 、 BoneCP开源连接池的比《转》

     简介   使用评价  项目主页  DBCP DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用 可以设置最大和最小连接,连接等待时 ...

  9. 开源DBCP、C3P0、Proxool 、 BoneCP连接池的比较

    简介 项目主页 使用评价  DBCP DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序用使用 http://homepages.nild ...

随机推荐

  1. 公司最喜欢问的Java集合类

    java.util包中包含了一系列重要的集合类,而对于集合类,主要需要掌握的就是它的内部结构,以及遍历集合的迭代模式. 接口:Collection Collection是最基本的集合接口,一个Coll ...

  2. Linux(CentOS)下编译安装apache

    Centos7.6系统 已经安装lnmp一键环境 想装个apache跑php7 (php7的安装以及与apache的交互在这里: https://www.cnblogs.com/lz0925/p/11 ...

  3. 实现单点登录功能的思路以及kafka同步数据

    单点登录以及用户数据同步思路与方案 当公司业务分布于多个子系统时, 同一用户在A系统注册,即可在其他所有关联系统使用, 并支持登录A系统后,自动在其他系统登录,退出同理. 在A平台修改通用的用户数据( ...

  4. 分布式消息中间件之kafka设计思想及基本介绍(一)

    Kafka初探 场景->需求->解决方案->应用->原理 我该如何去设计消息中间件--借鉴/完善 场景 跨进程通信(进程间生产消费模型) 需求 基本需求 实现消息的发送和接收. ...

  5. deep_learning_Function_tensorboard的使用

    数据可视化(网页能打开,但是没有数据):https://jingyan.baidu.com/article/e9fb46e1c55ac93520f7666b.html

  6. 记一次自启动的docker容器将宿主机的开机用户登录界面覆盖事件

    宿主机的系统为CentOS7_7.7.1908,默认为GUI启动,安装了宝塔面板,docker-ce为最新版. 在启动了一个centos7的容器(镜像为centos官方镜像)后,将该容器重启策略设置为 ...

  7. SQL Server中四类事务并发问题的实例再现(转)

    本篇文章将用实例再现数据库访问中四类并发问题,希望能让初学者能对事务的并行性有进一步的理解. 首先,让我们先来了解一下并行问题以及事务隔离级别这两个概念.在数据库中,假设如果没有锁定且多个用户同时访问 ...

  8. linux 默认为ipv6的话 ,如果设置ipv4?

    第一步:查出是ens33 第二步:修改文件 对比下方修改就ok TYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no BOOTPROTO=dhcp DEFRO ...

  9. MariaDB使用enum和set

    1.enum 单选字符串数据类型,适合存储表单界面中的“单选值”. 设定enum的时候,需要给定“固定的几个选项”:存储的时候就只存储其中的一个值. 设定enum的格式: enum("选项1 ...

  10. js 实现深拷贝

    在ECMAScript变量中包含两种不同类型的值:基本类型值和引用类型值. 基本类型值:Undefined.Null.Boolean.Number.String 引用类型值:Object.Array. ...