前言
实现基本的获取连接和归还连接的功能
自定义连接池
MockConnection
public class MockConnection implements Connection {
private String name;
public MockConnection(String name) {
this.name = name;
}
@Override
public String toString() {
return "MockConnection{" +
"name='" + name + '\'' +
'}';
}
// 省略实现的部分
}
Pool
package cn.knightzz.flyweight.pool;
import lombok.extern.slf4j.Slf4j;
import java.sql.Connection;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* @author 王天赐
* @title: Pool
* @projectName hm-juc-codes
* @description: 自定义连接池
* @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
* @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
* @create: 2022-08-20 19:51
*/
@Slf4j(topic = "Pool")
public class Pool {
/**
* 定义线程池大小
*/
private int poolSize;
/**
* 定义连接对象数组
*/
Connection[] connections;
/**
* 连接状态数组 0表示空闲, 1表示繁忙
*/
private AtomicIntegerArray status;
public Pool(int poolSize) {
// 检查poolSize是否合法
checkPoolSize(poolSize);
this.poolSize = poolSize;
this.connections = new Connection[poolSize];
this.status = new AtomicIntegerArray(poolSize);
// 初始化创建Connection
for (int i = 0; i < poolSize; i++) {
connections[i] = new MockConnection("Connection : " + i);
}
}
private void checkPoolSize(int poolSize) {
if (poolSize < 0) {
throw new RuntimeException("poolSize 非法 : poolSize = " + poolSize);
}
}
/**
* 获取连接
*
* @return 连接对象 java.sql.Connection
*/
public Connection borrow() {
// 基本思路 :
// 1. 遍历所有的状态数组, 获取状态为0的空闲连接
// 2. 如果有空闲连接直接返回 , 如果没有, 直接wait
// 3. 在最外层增加 while(true) 防止虚假唤醒
while (true) {
for (int i = 0; i < poolSize; i++) {
if (status.get(i) == 0) {
// 修改状态数组
// 这里要保证原子操作, 防止并发环境下 : 状态修改了, 但是没有连接, 或者同一个连接被重复获取
if (status.compareAndSet(i, 0, 1)) {
log.debug("获取连接成功 : {} ", connections[i]);
return connections[i];
}
}
}
// 如果暂时没有空闲连接, 就直接wait
// 这里需要注意的是, Pool 是多和线程共享同一个对象, wait操作需要加锁, 否则可能出现重复wait
synchronized (this) {
try {
log.debug("当前暂无空闲连接, wait中 .. .");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 释放连接
*
* @param connection 待释放掉的连接对象
*/
public void free(Connection connection) {
// 基本思路 :
// 1. 遍历所有的连接数组, 找到当前的连接
// 2. 设置当前的连接数组的状态
// 3. 唤醒所有等待的线程, 注意啊 : 要加锁, 防止多次重复唤醒
for (int i = 0; i < poolSize; i++) {
if (connections[i] == connection) {
// 修改状态
if (status.compareAndSet(i, 1, 0)) {
// 唤醒其他的线程
// 加锁避免重复notify
synchronized (this) {
log.debug("{} 释放连接成功, 唤醒其他线程 ", connection);
this.notifyAll();
}
break;
}
}
}
}
}
获取连接基本思路 :
- 遍历所有的状态数组, 获取状态为0的空闲连接
- 如果有空闲连接直接返回 , 如果没有, 直接wait
- 在最外层增加 while(true) 防止虚假唤醒
测试代码
package cn.knightzz.flyweight.pool;
import java.sql.Connection;
import java.util.Random;
@SuppressWarnings("all")
public class TestPool {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Connection conn = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.free(conn);
}, "t" + i).start();
}
}
}
总结
以上实现没有考虑:
-
连接的动态增长与收缩
-
连接保活(可用性检测)
-
等待超时处理
-
分布式 hash
对于关系型数据库,有比较成熟的连接池实现,例如c3p0, druid等 对于更通用的对象池,可以考虑使用apache
commons pool,例如redis连接池可以参考jedis中关于连接池的实现