前言
在了解到wait、notify的使用方式之后,我们使用wait、notify来实现一个连接池。如果还有不清楚wait、notify使用的,请进入传送门:https://segmentfault.com/a/1190000019104391
连接池原理
首先我们先来了解下连接池的基本原理
- 线程调用连接池的方法来获取连接
- 连接池内部判断连接池是否足够,如果足够则直接返回一个连接给线程
- 连接不够的情况下,判断连接池中是否允许新建连接(比如连接数小于最大连接数)。如果允许,则创建一个新的连接并返回给线程。
- 如果不允许新建连接,则判断是否允许等待空闲的连接,如果不允许,则未拿到连接
- 如果允许等待,之后就判断等待是否超时。如果超时,也是未拿到连接,如果未超时,则继续等待,一直循环。
代码实现
首先我们有一个空实现的连接类Connection
@Data
static class Connection {
private String connectionName;
public Connection(String connectionName) {
this.connectionName = connectionName;
}
}
此处只为了测试,该类只有一个名字属性。另外使用了lombok来自动生成set、get方法
接下来就是连接池的基本实现
@Data
public class ConnectionPoolOfWaitNotify {
private Integer capacity = 4;//连接池中的连接数
LinkedList<Connection> linkedList = new LinkedList<>(); //连接容器
public ConnectionPoolOfWaitNotify() {
IntStream.rangeClosed(1, capacity).forEach(i -> {//构造方法中初始化所有连接
linkedList.addLast(new Connection("connection-" + i));
});
}
//获取连接
public Connection getConnectioin(long time) throws InterruptedException {
synchronized (linkedList) {
if (!linkedList.isEmpty()) {//如果存在,拿走第一个
return linkedList.removeFirst();
}
if (time <= 0) {//等到拿到连接再返回
while (linkedList.isEmpty()) {
linkedList.wait();
}
return linkedList.removeFirst();
}
long lastTime = System.currentTimeMillis() + time;
long sleepTime = time;
while (linkedList.isEmpty() && sleepTime > 0) {
linkedList.wait(sleepTime);
sleepTime = lastTime - System.currentTimeMillis();
}
if (!linkedList.isEmpty()) {
return linkedList.removeFirst();
} else {
return null;
}
}
}
//归还连接
public void revertConnection(Connection connection) {
synchronized (linkedList) {
linkedList.addLast(connection);
linkedList.notifyAll();//归还连接后通知其他拿连接的线程
}
}
}
连接池中,主要有两个方法。一个是获取连接(可控制超时时间,超时时间小于等于0则永不超时),一个是归还连接。
主要的逻辑在获取连接的方法里面,当获取不到连接时,使用wait()方法来使得当前获取连接的线程进入等待状态。然后在归还连接成功之后,调用notifyAll()方法通知所有正在等待的线程可以继续获取连接了,但是继续获取连接时,还必须继续抢夺锁,只有占锁成功的线程,才能继续执行获取连接操作。
测试
之后执行以下测试代码
public static void main(String[] args) {
int allNum = 100;
AtomicInteger successNum = new AtomicInteger(0);
ConnectionPoolOfWaitNotify connectionPoolOfWaitNotify = new ConnectionPoolOfWaitNotify();
IntStream.rangeClosed(1, allNum).parallel().forEach(i -> {
Connection connection = null;
try {
connection = connectionPoolOfWaitNotify.getConnectioin(100);
if (null != connection) {
successNum.addAndGet(1);
System.out.println("线程" + i + "拿到连接" + connection.getConnectionName());
Thread.sleep(200);
} else {
System.out.println("线程" + i + "没有拿到连接");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != connection) {
connectionPoolOfWaitNotify.revertConnection(connection);
}
}
});
System.out.println("总共拿连接次数:" + allNum + ",拿到连接次数:" + successNum.get());
}
allNum为总共获取连接次数,successNum当中记录获取成功的次数。每次拿连接的超时时间为100毫秒,拿到连接后200的休眠(模拟业务处理)之后归还连接。而且在构造函数中直接初始化了所有的连接(读者可以考虑下如何做到按需来初始化的话)。运行后结果如下
线程23拿到连接connection-1
线程69没有拿到连接
线程10拿到连接connection-2
线程74拿到连接connection-4
线程25拿到连接connection-3
线程6没有拿到连接
线程70没有拿到连接
线程72没有拿到连接
线程71没有拿到连接
线程73没有拿到连接
线程75拿到连接connection-1
总共拿连接次数:100,拿到连接次数:25
100次只能拿到25次,那我们如果设置永不超时呢?调用方式如下,修改超时时间为0即可
connection = connectionPoolOfWaitNotify.getConnectioin(0);
之后再次运行结果如下
线程70拿到连接connection-1
线程58拿到连接connection-2
线程16拿到连接connection-3
线程60拿到连接connection-4
线程71拿到连接connection-1
线程59拿到连接connection-4
线程67拿到连接connection-3
线程68拿到连接connection-2
总共拿连接次数:100,拿到连接次数:100
OK,全部拿到,没毛病
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。