实现一个通用的池

在实际应用中,我们会接触诸如线程池,数据库连接池,各种池化技术实现的池,那是否可以实现一个通用的池,可以涵盖线程池,数据库连接池等等功能呢? 下面我们来尝试下,老规矩,我们先编写测试程序,通过测试程序看一下我们的需求是怎样的,然后该如何去实现这个通用的池.以数据库连接为例,

public class Main {
    public static void main(String[] args){
        Pool<Connection> pool = new GenericPool<Connection>(xxx,
                xxx,
                xxx);
        Connection connection = pool.borrowObject();
        //业务处理
        //.......................
        pool.returnObject(connection);

        //关闭池
        pool.shutdown();
    }
}

从上我们可以总结出一个池它应该有的基本功能如下:

1,允许程序从池中"借"走一个元素

2,有借有还,程序用完后需要归还元素到池中

3,池关闭

从上可以看出,一个池的基本行为有借,还,关闭三种行为,so,我们首先定义一个接口,用于描述这种行为:

public interface Pool<T> {
    /**
     * 获取对象
     * @return
     */
    public T borrowObject();

    /**
     * 归还对象
     * @param t
     */
    public void returnObject(T t);

    /**
     * 关闭池
     */
    void shutdown();
}
为了使我们的池通用,采用了泛型来描述被池化的对象。接下来,我们得想下,该如何去实现这个接口,并且我们的实现能够尽可能的通用。

首先分析第一种行为 “”借”,当从池中取出元素时,我们需要校验下该元素是否可用,有可能在池里待待久了,某些属性或行为已经发生了变化,而元素却没有及时感知到

第二中行为,同上,在放入池子中之前,需要校验下该池化对象是否可用

第三种行为,池关闭依赖于池的具体实现,所以该方法需要放置在具体的实现类中

基于上面的分析,我们很容易发现其中存在共性,我们将这些共性抽取出来,提供一个基础的实现:

public abstract class AbstractPool<T> implements Pool<T> {

    /**
     * 从池中取出来的时候,需要校验当前的对象是否可用
     * @return
     */
    @Override
    public T borrowObject() {
        T t = borrowObjectFromPool();
        if(isValid(t)){
            return t;
        }else{
            handleInvalidObject(t);
        }
        return null;
    }

    /**
     * 同样在归还的时候需要校验当前对象是否可用
     * @param t
     */
    @Override
    public void returnObject(T t) {
        if(isValid(t)){
            returnObjectToPool(t);
        }else{
            handleInvalidObject(t);
        }
    }

    protected abstract void returnObjectToPool(T t);
    protected abstract void handleInvalidObject(T t);
    protected abstract boolean isValid(T t);
    protected abstract T borrowObjectFromPool();
}

再考虑下上面的几个abstract方法,其中借出和归还两个行为已经非常通用了,略过,主要思考下剩余的两个方法。判断一个对象是否有效的方法需要扩展下。比如如果这个池化的对象是数据库连接,那么判断是否有效时需要判断连接是否已关闭等,如果池化的对象是线程,那需要判断线程是否已中断。总之因为池化对象的不同,对应着不同的实现,handleInvalidObject方法也是同样的道理。所以我们新增一个接口,用于描述这两个行为

public interface Validator<T> {

    public boolean isValid(T t);

    public void invalidate(T t);
}
这样,我们再构造对象池的时候,传入对应的Validator实现即可。回过头来,我们思考下shutdown方法,该方法也是依赖于具体池实现,那为什么不抽出一个接口来描述下这个行为呢?其实没有必要,因为在关闭池时,可以直接调用validator的invalidate方法清理池化的对象。接下来,就应该考虑如果去实现我们的池的功能了,这里我定义类GenericPool为我们的池,思考下我们这个池应该是怎样的。其实很简单,既然有了class,class中无外乎就包含成员变量和方法了,我们首先来看下它应该包含哪些成员变量:

1,池大小,能够容纳多少个对象

2,对象该放在何处呢,需要一个数据结构能够方便的进行操作

3,根据上面的描述,我们需要一个Validator来处理valid/invalid操作

4,也是最重要的一点,对象该如何产生呢,这个池需要知道对象是怎样产生的

至于这个类应该有哪些方法就不必再说了,上文中的AbstractPool已经能很清楚的说明了。

下面我们挨个解决上面的问题:

1,定义变量size,用于表示池的大小

2,定义变量pooledObjects,其类型为LinkedBlockingQueue,用于存放被池化的对象

3,定义变量validator,类型为Validator,需要有具体的实现

4,考虑到不同对象,产生的方式可能不用,这里我们将这个行为抽出来,定义一个接口

public interface ObjectFactory<T> {

    public T makeObject();
}
到此,我们就知道了GenericPool类大致应该是这样的:
public class GenericPool<T> extends AbstractPool<T> {

    private int size;//池大小
    private BlockingQueue<T> pooledObjects;//池化的对象存放在哪里
    private Validator<T> validator;
    private ObjectFactory<T> objectFactory;

    public GenericPool(int size, Validator<T> validator, ObjectFactory<T> objectFactory ){
        this.size = size;
        this.pooledObjects = new LinkedBlockingQueue<T>();
        this.validator = validator;
        this.objectFactory = objectFactory;
        init();
    }
}
构造函数中init方法用于初始化池,向池中放入size个对象
private void init() {
        for(int i=0;i<size;i++){
            T t = objectFactory.makeObject();
            pooledObjects.add(t);
        }
    }

接下来,就是实现剩下的几个方法了:

@Override
    public void shutdown() {
        for(T t : pooledObjects){
            validator.invalidate(t);
        }
        pooledObjects.clear();
        pooledObjects = null;
        validator = null;
        objectFactory = null;
    }

    @Override
    protected void returnObjectToPool(T t) {
        if(isValid(t)){
            pooledObjects.add(t);
        }else{
            handleInvalidObject(t);
        }
    }

    @Override
    protected void handleInvalidObject(T t) {
        validator.invalidate(t);
    }

    @Override
    protected boolean isValid(T t) {
        return validator.isValid(t);
    }

    @Override
    protected T borrowObjectFromPool() {
        T t = null;
        try {
            t = pooledObjects.take();
        } catch (InterruptedException e) {
            throw new RuntimeException("interrupt happens during borrow object");
        }
        return t;
    }



到此,主要的功能我们都实现了,还缺少的是Validator和ObjectFactory的具体实现了,这里我们首先以数据库连接为例,给个例子:

public class JdbcConnectionFactory implements ObjectFactory<Connection> {

    static {
        try {
            //加载驱动类
            Class.forName("org.postgresql.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private String url;
    private String userName;
    private String password;

    public JdbcConnectionFactory(String url,String name,String pwd){
        this.url = url;
        this.userName = name;
        this.password = pwd;
    }
    @Override
    public Connection makeObject() {
        try {
            return DriverManager.getConnection(url,userName,password);
        } catch (SQLException e) {
            throw new RuntimeException("make object failed");
        }
    }
}

public class JdbcConnectionValidator implements Validator<Connection> {
    @Override
    public boolean isValid(Connection connection) {
        try {
            return null!=connection && !connection.isClosed();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public void invalidate(Connection connection) {
        if(null != connection){
            try{
                connection.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

接下来,更改下我们的测试类Main,我们看下测试的效果

public static void main(String[] args){
        Pool<Connection> pool = new GenericPool<Connection>(3,
                new JdbcConnectionValidator(),
                new JdbcConnectionFactory("jdbc:postgresql://localhost:5432/postgres","postgres","postgres"));
        //业务处理
        for(int i=0;i<10;i++){
            Connection connection = pool.borrowObject();
            System.out.println("第"+i+"次获取到的连接:"+connection.toString());
            pool.returnObject(connection);
        }
        //.......................

        //关闭池
        pool.shutdown();
我们在池放三个元素,然后模拟10次获取池化的对象,结果如下:
第0次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@7fca02
第1次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@1d8b871
第2次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@1026d9f
第3次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@7fca02
第4次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@1d8b871
第5次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@1026d9f
第6次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@7fca02
第7次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@1d8b871
第8次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@1026d9f
第9次获取到的连接:org.postgresql.jdbc4.Jdbc4Connection@7fca02

上面关于数据库连接的池化例子应该适用于绝大多数场景,但有一个场景不适合,那就是线程,主要有以下几个方面的问题:

1,线程一旦启动开始运行后,不能让线程退出,否则没法池化

2,获取池化的对象后,要能够让其执行对应的任务

基于以上两点,我们需要对线程进行封装,我们首先自定义池化的线程类PoolThread, 结构如下:

public class PoolThread {
    private LinkedBlockingQueue<Runnable> task = new LinkedBlockingQueue<Runnable>();

    private boolean started = false;

    private boolean stopped = false;

    private Thread thread = new Thread("poolThread-"+this.toString()){
        @Override
        public void run() {
            while(!stopped){
                try {
                    Runnable runnable = task.take();
                    runnable.run();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };


    public void run(Runnable runnable){
        task.add(runnable);
        if(!started){
            thread.start();
            started = true;
        }
    }

    public Thread getThread() {
        return thread;
    }

    public void setStopped(boolean stopped) {
        this.stopped = stopped;
    }
}


一个PoolThread对应一个真正的Thread,然后维护了一个队列,让真正的线程不停的从队列中获取任务,这样我们的PoolThread就可以被真正池化了。接下来,我们看下对应的Validator和ObjectFactory实现:

public class PoolThreadFactory implements ObjectFactory<PoolThread> {
    @Override
    public PoolThread makeObject() {
        return new PoolThread();
    }
}

public class PoolThreadValidator implements Validator<PoolThread> {
    @Override
    public boolean isValid(PoolThread poolThread) {
        return !poolThread.getThread().isInterrupted();
    }

    @Override
    public void invalidate(PoolThread poolThread) {
        poolThread.getThread().interrupt();//中断线程
        poolThread.setStopped(true);
        LinkedBlockingQueue<Runnable> runnables = poolThread.getTask();
        runnables.clear();
        runnables = null;

    }
}


接下来,我们更改下测试类,看下效果:

Pool<PoolThread> pool = new GenericPool<PoolThread>(3,new PoolThreadValidator(),new PoolThreadFactory());
      for(int i=0;i<10;i++){
          PoolThread thread = pool.borrowObject();
          thread.run(new Runnable() {
              @Override
              public void run() {
                  System.out.println("task run in the thread:"+Thread.currentThread());
              }
          });
          try {
              Thread.sleep(1000);//等一秒再归还
          }catch (Exception e){
              e.printStackTrace();
          }
          pool.returnObject(thread);
      }
        //关闭池
        pool.shutdown();
    }

同样在池中放了三个线程,模拟十次请求,执行结果如下:

task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@1d74bb1,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@1aa7a6a,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@14ae5cd,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@1d74bb1,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@1aa7a6a,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@14ae5cd,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@1d74bb1,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@1aa7a6a,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@14ae5cd,5,main]
task run in the thread:Thread[poolThread-com.java.pool.test.PoolThread@1d74bb1,5,main]

ok,至此,池的功能我们实现了,下面我们从各个角度来思考下我们上面所实现的池的正确性,健壮性

1,异常处理,假设池中的对象有较长的时间未使用,并且已经失效但对象还未感知到,这样在数次调用borrow后,会导致线程卡在这里,该如何解决?

出现这种情况,如果发现pooledObjects队列为空,此时应该再次调用init方法,初始化池中的对象.同时必要的监控手段也要有

2,并发问题,是否线程安全?

比如多个线程调用shutdown方法,会出现并发问题,所以这里需要改造下:

public void synchronized shutdown() {  
	if(!shutdownCancelled){
	  for(T t : pooledObjects){  
		validator.invalidate(t);  
	  }  
	  pooledObjects.clear();  
	  pooledObjects = null;  
	  validator = null;  
	  objectFactory = null; 
	}
         
    }  

用一个变量标识池是否已关闭,然后方法被关键字synchronized修饰即可

3,还有个问题就是在归还对象的时候,如果对同一个池化的对象做了多次归还,那么池中会有多个相同的对象,如何解决?

归还时先判断下队列中是否已经包含改对象,同时方法上应该加上synchronized关键字

4,假如程序员粗心,万一忘记归还操作,那这个池岂不是没有起到作用?

这个问题从java层面看,暂时还没有想到好的办法,只能依靠程序员细心点,以及请别人共同code review的方式了。先写在这里,回头想到好的办法后再来补充.



猜你喜欢

转载自blog.csdn.net/chengzhang1989/article/details/78571177