作为一个开发者,我们在写一个基础组件的时候需要经过一些步骤和思考才能正式的编码。
1.分析需求
连接池是用来解决什么问题的?
方便管理,降低性能损耗。
2.概要设计
初始化连接池(最大数量,超时时间)
获取连接
释放连接
3.TDD
驱动测试开发,而不是写完具体的实现类之后才去写测试,这样你内心会有意识的去避免一些坑,而是测试写完之后再去开发。
4.技术选型
状态:空闲,繁忙两种状态需要放在集合之中,最终选Queue
List:无序,可重复
Queue:队列,先进先出,后进后出(这个是我们是想要的方式,方便将空闲太久的连接取出来)
以下是队列中常用的api方法
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞
vactor:线程安全的
ArrayList:常用,非线程安全
stack:栈的形式,后进先出(跟我们的需求不符合)
set:无序,不重复(用不到特性)
map:k/v形式存储(用不到)
5.实际编码
初始化
传入最大连接数量,以及超时时间,
创建两个队列,一个存放空闲,一个存放繁忙
创建一个AtomicInteger(线程安全的),用于记录创建连接的数量
获取连接
1.进入方法,记录时间,后面判断超时使用
2.在空闲队列获取连接
3.如果获取到连接,将其放在繁忙队列,同时返回连接
4.获取不到,就创建连接(前提需要判断是否已经达到最大连接数)
5.没有达到最大数量,就创建连接,同时放入繁忙队列,同时将连接返回
6.如果已经达到最大数量,继续往下执行,等待空闲队列非空,同时传入一个等待时间(最大时间-(现在秒数-刚才秒数))
7.判断是否在空闲队列中获取到,如果获取到,放入繁忙队列,将连接返回。
8.获取的连接为空,判断是否已经超时,如果超时跑出异常,没有超时,进行while循环获取连接。
释放连接
1.传入一个连接,将其从繁忙队列中删除
2.将这个连接放入空闲队列
3.如果删除异常,则说用这个连接已经失效,将记录创建连接的数量减一。
6.redis连接池的具体实现:
1.概要设计,面向接口开发
package com.dudu.lizhen.redistest; import redis.clients.jedis.Jedis; /** * Created by Administrator on 2018/5/26. */ public interface RedisPool { //1.初始化线程池的大小,以及规定连接的超时时间 /** * 定义一个线程池 * @param max 最大数量 * @param timeOut 超时时间 */ public void init(int max,long timeOut); //2.获取连接 /** * 获取连接 * @return */ public Jedis browResource() throws Exception; //3.释放连接 /** * 释放连接 */ public void releace(Jedis jedis); }
2.TDD测试驱动开发(具体的实现类可以空,不用先执行,等后面我们写完具体的实现,便可直接执行以下代码)
package com.dudu.lizhen.redistest; import redis.clients.jedis.Jedis; import java.util.concurrent.CountDownLatch; /** * Created by Administrator on 2018/5/27. */ public class RedisPoolTest { private static final CountDownLatch countDownLatch = new CountDownLatch(50); public static void main(String[] args) { final RedisPool redisPool = new RedisPoolImp(); //初始化连接 redisPool.init(10, 2000); for (int i = 0; i < 50; i++) { new Thread(new Runnable() { @Override public void run() { //每次减一 countDownLatch.countDown(); try { //所有线程都在此等待 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } for (int a = 0; a < 10; a++) { Jedis jedis = null; try { //获取连接 jedis = redisPool.browResource(); jedis.incr("name"); } catch (Exception e) { e.printStackTrace(); } finally { //最终释放连接 redisPool.releace(jedis); } } } }).start(); } } }
3.redis连接的具体实现,我们写的时候最好先将伪代码写完之后,根据伪代码的顺序去开发,写完我们只要将测试类的具体实现一换,便可测试。
package com.dudu.lizhen.redistest; import redis.clients.jedis.Jedis; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * Created by Administrator on 2018/5/26. */ public class RedisPoolImp implements RedisPool { //最大连接数量 private int max; //超时时间 private long timeOut; //空闲连接 private LinkedBlockingQueue<Jedis> ide; //繁忙连接 private LinkedBlockingQueue<Jedis> busy; //用于计数,保证线程安全。 AtomicInteger atomicInteger = new AtomicInteger(0); //用于打印创建连接的次数 AtomicInteger count = new AtomicInteger(0); /** * 连接池初始化 * * @param max 最大数量 * @param timeOut 超时时间 */ @Override public void init(int max, long timeOut) { this.max = max; this.timeOut = timeOut; ide = new LinkedBlockingQueue<Jedis>(); busy = new LinkedBlockingQueue<Jedis>(); } @Override public Jedis browResource() throws Exception { //伪代码 //50个线程,只允许10个获得连接 //需要有两个集合,一个已经繁忙集合,一个空闲集合 //1.进入方法,记录时间,以备以后超时使用 //2.在空闲线程中获取连接,如果非空直接返回连接 // 3.为空继续往下执行,判断最大线程是否已经满了, // 4.如果没满就创建,然后放在繁忙线程中 //5.如果满了,就等待空闲线程非空 //6.如果过了超时时间就抛出异常 //1.进入方法,记录时间,以备以后超时使用 long nowTime = System.currentTimeMillis(); Jedis jedis = null; while (null == jedis) { //2.在空闲线程中获取连接,如果非空,将其存放在繁忙线程,直接返回连接, jedis = ide.poll(); if (null != jedis) { busy.offer(jedis); return jedis; } // 3.为空继续往下执行,判断最大线程是否已经满了, if (atomicInteger.get() < max) { // 4.如果没满就创建,然后放在繁忙线程中 if (atomicInteger.incrementAndGet() <= max) { jedis = new Jedis("127.0.0.1", 6379); jedis.auth("admin"); System.out.println("jedis的连接数量:" + count.incrementAndGet()); busy.offer(jedis); return jedis; } else { //因为在前面if中加1,所以后面需要进行减1 atomicInteger.decrementAndGet(); } } //5.如果满了,就等待空闲线程非空 try { jedis = ide.poll(timeOut - (System.currentTimeMillis() - nowTime), TimeUnit.MILLISECONDS); } catch (Exception e) { e.printStackTrace(); } //7.确定没有超时,返回连接 if (null != jedis) { busy.offer(jedis); return jedis; }else{ //6.如果过了超时时间就抛出异常 if ((System.currentTimeMillis() - nowTime) > timeOut) { throw new Exception("超时了。。。。"); }else{ continue; } } } System.out.println("能否走到这。。。。。。。"); return null; } @Override public void releace(Jedis jedis) { //释放连接 //1.从繁忙中移除 if (busy.remove(jedis)) { //2.加入空闲 ide.offer(jedis); } else { //3.如果移除失败,说明连接已经不可用,然后计数器减一 atomicInteger.decrementAndGet(); } } }