首页 > 编程语言 > Java 模拟数据库连接池的实现代码
2021
02-26

Java 模拟数据库连接池的实现代码

前面学习过等待 - 通知机制,现在我们在其基础上添加一个超时机制,模拟从连接池中获取、使用和释放连接的过程。客户端获取连接的过程被设定为等待超时模式,即如果在 1000 毫秒内无法获取到可用连接,将会返回给客户端一个 null。设定连接池的大小为 10 个,然后通过调节客户端的线程数来模拟无法获取连接的场景

由于 java.sql.Connection 只是一个接口,最终实现是由数据库驱动提供方来实现,考虑到本例只是演示,我们通过动态代理构造一个 Connection,该 Connection 的代理仅仅是在调用 commit() 方法时休眠 100 毫秒

public class ConnectionDriver {

  static class ConnectionHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("commit".equals(method.getName())) {
        TimeUnit.MICROSECONDS.sleep(100);
      }
      return null;
    }
  }

  /**
   * 创建一个 Connection 的代理,在 commit 时休眠 100 毫秒
   */
  public static Connection createConnection() {
    return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),
        new Class<?>[]{Connection.class}, new ConnectionHandler());
  }
}

接下来是线程池的实现。本例通过一个双向队列来维护连接,调用方需要先调用 fetchConnection(long) 方法来指定在多少毫秒内超时获取连接,当连接使用完成后,需要调用 releaseConnection(Connection) 方法将连接放回线程池

public class ConnectionPool {

  private final LinkedList<Connection> pool = new LinkedList<>();

  public ConnectionPool(int initialSize) {
    // 初始化连接的最大上限
    if (initialSize > 0) {
      for (int i = 0; i < initialSize; i++) {
        pool.addLast(ConnectionDriver.createConnection());
      }
    }
  }

  public void releaseConnection(Connection connection) {
    if (connection != null) {
      synchronized (pool) {
        /* 连接释放后需要进行通知
         * 这样其他消费者就能知道连接池已经归还了一个连接
         */
        pool.addLast(connection);
        pool.notifyAll();
      }
    }
  }

  /**
   * 在给定毫秒时间内获取连接
   */
  public Connection fetchConnection(long mills) throws InterruptedException {
    synchronized (pool) {
      // 完全超时
      if (mills < 0) {
        while (pool.isEmpty()) {
          pool.wait();
        }
        return pool.removeFirst();
      } else {
        long future = System.currentTimeMillis() + mills;
        long remaining = mills;
        while (pool.isEmpty() && remaining > 0) {
          pool.wait(remaining);
          remaining = future - System.currentTimeMillis();
        }
        Connection result = null;
        if (!pool.isEmpty()) {
          result = pool.removeFirst();
        }
        return result;
      }
    }
  }
}

最后编写一个用于模拟客户端获取连接的示例,该示例将模拟多个线程同时从连接池获取连接,并记录总尝试获取数、获取成功数和获取失败数

public class ConnectionPoolTest {

  static ConnectionPool pool = new ConnectionPool(10);
  static CountDownLatch start = new CountDownLatch(1);
  static CountDownLatch end;

  public static void main(String[] args) throws InterruptedException {
    // 线程数量
    int threadCount = 200;
    end = new CountDownLatch(threadCount);
    int count = 20;
    AtomicInteger got = new AtomicInteger();
    AtomicInteger notGot = new AtomicInteger();
    for (int i = 0; i < threadCount; i++) {
      Thread thread = new Thread(new ConnectionRunner(count, got, notGot), "ConnectionRunnerThread");
      thread.start();
    }
    start.countDown();
    end.await();
    System.out.println("total invoke : " + (threadCount * count));
    System.out.println("got connection : " + got);
    System.out.println("not got connection : " + notGot);
  }

  static class ConnectionRunner implements Runnable {

    int count;
    AtomicInteger got;
    AtomicInteger notGot;

    public ConnectionRunner(int count, AtomicInteger got, AtomicInteger notGot) {
      this.count = count;
      this.got = got;
      this.notGot = notGot;
    }

    @Override
    public void run() {
      try {
        start.await();
      } catch (Exception e) {
        e.printStackTrace();
      }
      while (count > 0) {
        try {
          // 从线程池中获取连接,如果 1000ms 内无法获取到,将返回 null
          // 分别统计获取连接的数量 got 和未获取到的数量 notGot
          Connection connection = pool.fetchConnection(1000);
          if (connection != null) {
            try {
              connection.createStatement();
              connection.commit();
            } finally {
              pool.releaseConnection(connection);
              got.incrementAndGet();
            }
          } else {
            notGot.incrementAndGet();
          }
        } catch (Exception e) {
          e.printStackTrace();
        } finally {
          count--;
        }
      }
      end.countDown();
    }
  }
}

笔者设置线程数量为 200 时,得出结果如下

当设置为 500 时,得出结果如下,当然具体结果根据机器性能而异

可见,随着客户端线程数的增加,客户端出现超时无法获取连接的比率不断升高。这种等待超时模式能保证程序出问题时,线程不会一直运行,而是按时返回,并告知客户端获取连接出现问题。数据库连接池的实际也可以应用到其他资源获取的场景,针对昂贵资源的获取都应该加以限制

到此这篇关于Java 模拟数据库连接池的实现代码的文章就介绍到这了,更多相关Java 数据库连接池内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧