例如:一个线上商城应用,QPS 达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。 这时预先创建好一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
@Slf4j(topic = "c.Pool")
class Pool{
//1.连接池大小
private final int poolSize;
//2.连接对象数组
private Connection[] connections;
//3.连接状态数组 0空闲 1繁忙 需要是线程安全的
private AtomicIntegerArray states;
//4.构造方法初始化
public Pool(int poolSize) {
this.poolSize = poolSize;
this.connections = new Connection[poolSize];
this.states = new AtomicIntegerArray(new int[poolSize]);
for (int i = 0; i < poolSize; i++) {
connections[i] = new MockConnection("连接"+(i+1));
}
}
//5.借连接
public Connection borrow(){
while(true){
for (int i = 0; i < poolSize; i++) {
if (states.get(i) == 0) {
if (states.compareAndSet(i,0,1)) {
log.debug("borrow{}",connections[i]);
return connections[i];
}
}
}
//如果没有空闲连接了,让当前线程进行等待
synchronized (this){
try {
log.debug("wait...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//6.归还连接
public void free(Connection connection){
for (int i = 0; i < poolSize; i++) {
if (connections[i] == connection) {
states.set(i,0);//归还连接不用cas操作
synchronized (this){//归还过后唤醒等待线程竞争
log.debug("free{}",connection);
this.notifyAll();
}
break;
}
}
}
}
class MockConnection implements Connection{
private String name;
public MockConnection(String name) {
this.name = name;
}
...省略了实现的方法
}
测试代码:
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);
}).start();
}
}
某次运行结果:
以上实现没有考虑:
-
连接的动态增长与收缩
-
连接保活(可用性检测)
-
等待超时处理
-
分布式 hash
对于关系型数据库,有比较成熟的连接池实现,例如c3p0, druid等
对于更通用的对象池,可以考虑使用apachecommons pool,例如redis连接池可以参考jedis中关于连接池的实现