2

前言

在了解到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,全部拿到,没毛病


国子监阿创
131 声望8 粉丝

任何一个傻瓜都能写出计算机能理解的程序,而优秀的程序员却能写出别人能读得懂的程序。—— Martin Fowler