【面试:并发篇36:多线程:设计模式】享元模式-应用:自定义连接池
00.前言
如果有任何问题请指出,感谢。
01.介绍
我们现在设想一个场景,假如你有一个网站 QPS达到数千,如果每次都重新创建和关闭数据库连接池,性能会受到很大的影响,这时我们预先创建好一批连接,放入连接池。一次请求到达后从连接池获取连接,使用完毕后再还回连接池,这样即节约了连接和关闭的时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库。
02.连接池代码
public class Test3 {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
MockConnection conn = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.free(conn);
}).start();
}
}
}
@Slf4j(topic = "c.Pool")
class Pool {
// 1. 连接池大小
private final int poolSize;
// 2. 连接对象数组
private MockConnection[] connections;
// 3. 连接状态数组 0 表示空闲, 1 表示繁忙
private AtomicIntegerArray states;
// 4. 构造方法初始化
public Pool(int poolSize) {
this.poolSize = poolSize;
this.connections = new MockConnection[poolSize];
this.states = new AtomicIntegerArray(new int[poolSize]);
for (int i = 0; i < poolSize; i++) {
connections[i] = new MockConnection("连接" + (i+1));
}
}
// 5. 借连接
public MockConnection 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(MockConnection conn) {
for (int i = 0; i < poolSize; i++) {
if (connections[i] == conn) {
states.set(i, 0);
synchronized (this) {
log.debug("free {}", conn);
this.notifyAll();
}
break;
}
}
}
}
class MockConnection {
private String name;
public MockConnection(String name) {
this.name = name;
}
@Override
public String toString() {
return "MockConnection{" +
"name='" + name + '\'' +
'}';
}
}
结果
18:28:02.779 c.Pool [Thread-0] - wait…
18:28:02.778 c.Pool [Thread-3] - borrow MockConnection{name=‘连接2’}
18:28:02.786 c.Pool [Thread-4] - wait…
18:28:02.786 c.Pool [Thread-1] - wait…
18:28:02.778 c.Pool [Thread-2] - borrow MockConnection{name=‘连接1’}
18:28:03.325 c.Pool [Thread-3] - free MockConnection{name=‘连接2’}
18:28:03.325 c.Pool [Thread-1] - borrow MockConnection{name=‘连接2’}
18:28:03.325 c.Pool [Thread-4] - wait…
18:28:03.325 c.Pool [Thread-0] - wait…
18:28:03.519 c.Pool [Thread-1] - free MockConnection{name=‘连接2’}
18:28:03.519 c.Pool [Thread-4] - wait…
18:28:03.519 c.Pool [Thread-0] - borrow MockConnection{name=‘连接2’}
18:28:03.521 c.Pool [Thread-2] - free MockConnection{name=‘连接1’}
18:28:03.521 c.Pool [Thread-4] - borrow MockConnection{name=‘连接1’}
18:28:03.554 c.Pool [Thread-4] - free MockConnection{name=‘连接1’}
18:28:04.365 c.Pool [Thread-0] - free MockConnection{name=‘连接2’}
解释
MockConnection类
连接池类
Pool类
1.states表示的是连接状态数组,如果第i个连接被用了就把states的cas方法把0 改成 1表示已经被用了
2.borrow()方法 用来从连接池中获取连接里面是一个while循环 如果此时有空闲连接就返回空闲连接,如果没有则wait进入等待,等待之后的其他线程归还连接后唤醒
3.free()方法 用来遍历当前连接池连接 如果当前线程归还的连接存在则 states.set(i, 0); 把这个连接重置为0 表示没有线程使用,并且notifyAll唤醒所有线程 然后其他在等待的线程被唤醒
注意
1.为什么borrow()方法 要进行wait 通过代码可知我们不加wait/notifyAll 这个代码一样没有问题,其实这里加wait的真正作用是防止while循环空转 降低cpu效率,加上wait后进入等待 不占用cpu时间片
2.为什么free()方法 要在if里面执行notifyAll 而不放在for循环外,原因是 有这么一种可能 假如有人创建了一个不属于这个连接池的连接 然后归还了 如果此时notifyAll放在了for循环外 就会导致连接池并没有空闲连接但是还是唤醒了所有线程