jedisPool实现原理及源码分析(1)----对象池的说明

时间:2021-09-05 17:27:27

redis的并发竞争问题如何解决?

  Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:

1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
2.服务器角度,利用setnx实现锁。
对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;

这里我要提到的是jedis的池化,即jedisPool

      对象的池化技术:

   我们都知道一个对象比如car(对象)在其生命周期大致可氛围"创建","使用","销毁"三大阶段,那么它的总时间就是T1(创建)+T2(使用)+T3(销毁),如果创建N个对象都需要这样的步骤的话是非常耗性能的,就算JVM对垃圾回收机制有优化,但"创建"和"销毁"多少总会占用部分资源,那么我们就会想能否像常量池那样,让对象可复用,从而减少T1和T3所消耗的资源呢?这就引出了我们今天的内容-----对象池化技术即ObjectPool  (jedis也是一个Object对象,我们下面先介绍对象池)

  官网对对象池的解释是:

  将用过的对象保存起来,等下次需要这种对象的时候再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销,用于充当保存对象的"容器"对象,被称为"对象池"。

      对于没有状态的对象,String,在重复使用之前,无需进行任何处理,对于有状态的对象如StringBuffer,在重复使用之前,需要恢复到同等于生成的状态,如果恢复不了的话,就只能丢掉,改用创建的实例了。并非所有对象都可以用来池化,因为维护池也需要开销,对生成时开销不大的对象进行池化,它可能降低性能。

 

在上述的对对象池的描述来看,在看源码之前我问了自己一个问题,如果让你来做对象池你觉的应该注意什么?

  我们举个例子,所谓对象池嘛肯定是管理对象的,我们将对象看成是一台公共自行车,将对象池比作一个自行车站点。那么我们来想象着,我们怎么可以设计对象池

      首先对于这个站点的功能我们猜想,肯定有"借"自行车,"还"自行车,定期"检查"自行车车况,"销毁"(坏了的,拿去修或者销毁)这几个基本的功能。
      那对于自行车呢?站点肯定不能自己造自行车,肯定需要一个自行车工厂去维护自行车,那么自然我们想想这个工厂会有哪些功能

  首先作为工厂,那么肯定会有"生产"功能,"检测"功能,"出库"功能,"销毁"功能。

 有了上述的假象之后,我们带着这些概念,看看真正的代码是怎么设计的,是不是跟我想的有点类似,下面我们就来看源代码

  PooledObjectFactory、ObjectPool和ObjectPoolFactory 
  在Pool组件中,对象池化的工作被划分给了三类对象: 

  • PooledObjectFactory用于管理被池化的对象的产生、激活、挂起、校验和销毁;
  • ObjectPool用于管理要被池化的对象的借出和归还,并通知PoolableObjectFactory完成相应的工作;
  • ObjectPoolFactory则用于大量生成相同类型和设置的ObjectPool。

  相应地,使用Pool组件的过程,也大体可以划分成“创立PooledObjectFactory”、“使用ObjectPool”和可选的“利用ObjectPoolFactory”三步。

       PooledObjectFactory

   是一个接口,因为工程可以多多种工厂,自行车,客车,卡车等等。该接口jdk没有默认实现,需要自己实现。点开对象的工厂,我惊奇的发现,完全验证了我的猜想

  只是在基础功能的基础上,多增加了一些其他的功能罢了。

import org.apache.commons.pool.PoolableObjectFactory;

// 1) 创建一个实现了PoolableObjectFactory接口的类。
public class PoolableObjectFactorySample implements PoolableObjectFactory {

    private static int counter = 0;

    // 2) 为这个类添加一个Object makeObject()方法。这个方法用于在必要时产生新的对象。
    public Object makeObject() throws Exception {
        Object obj = String.valueOf(counter++);
        System.err.println("Making Object " + obj);
        return obj;
    }

    // 3) 为这个类添加一个void activateObject(Object obj)方法。这个方法用于将对象“激活”——设置为适合开始使用的状态。
    public void activateObject(Object obj) throws Exception {
        System.err.println("Activating Object " + obj);
    }

    // 4) 为这个类添加一个void passivateObject(Object obj)方法。这个方法用于将对象“挂起”——设置为适合开始休眠的状态。
    public void passivateObject(Object obj) throws Exception {
        System.err.println("Passivating Object " + obj);
    }

    // 5) 为这个类添加一个boolean validateObject(Object obj)方法。这个方法用于校验一个具体的对象是否仍然有效,已失效的对象会被自动交给destroyObject方法销毁。
    public boolean validateObject(Object obj) {
        /* 以1/2的概率将对象判定为失效 */
        boolean result = (Math.random() > 0.5);
        System.err.println("Validating Object "
                + obj + " : " + result);
        return result;
    }

    // 6) 为这个类添加一个void destroyObject(Object obj)方法。这个方法用于销毁被validateObject判定为已失效的对象。
    public void destroyObject(Object obj) throws Exception {
        System.err.println("Destroying Object " + obj);
    }

}

 

    ObjectPool

 

  有了合适的PooledObjectFactory之后,便可以开始请出ObjectPool来与之同台演出了。 
ObjectPool是在org.apache.commons.pool包中定义的一个接口,实际使用的时候也需要利用这个接口的一个具体实现。Pool组件本身包含了若干种现成的ObjectPool实现,

StackObjectPool :

StackObjectPool利用一个java.util.Stack对象来保存对象池里的对象。这种对象池的特色是: 

    • 可以为对象池指定一个初始的参考大小(当空间不够时会自动增长)。
    • 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
    • 可以为对象池指定一个可保存的对象数目的上限。达到这个上限之后,再向池里送回的对象会被自动送去回收。

SoftReferenceObjectPool 

SoftReferenceObjectPool利用一个java.util.ArrayList对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(Soft Reference)。这种对象池的特色是: 

    • 可以保存任意多个对象,不会有容量已满的情况发生。
    • 在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
    • 可以在初始化同时,在池内预先创建一定量的对象。
    • 当内存不足的时候,池中的对象可以被Java虚拟机回收。

GenericObjectPool----jedis就是基于这个的

GenericObjectPool利用一个org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。这种对象池的特色是: 

    • 可以设定最多能从池中借出多少个对象。
    • 可以设定池中最多能保存多少个对象。
    • 可以设定在池中已无对象可借的情况下,调用它的borrowObject方法时的行为,是等待、创建新的实例还是抛出异常。
    • 可以分别设定对象借出和还回时,是否进行有效性检查。
    • 可以设定是否使用一个单独的线程,对池内对象进行后台清理。

 

 

可以直接利用。如果都不合用,也可以根据情况自行创建。具体的创建方法,可以参看Pool组件的文档和源码。

  我这边总结了下,对代码做了一个简化的修改

  我们可以看到,这个对象池就跟我们之前猜想的站点的功能一样,主要的方法就是一个借borrowObject(),还returnObject()功能。

 

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPool;

public class ObjectPoolSample {

    public static void main(String[] args) {
        Object obj = null;

        // 1) 生成一个要用的PoolableObjectFactory类的实例。
        PoolableObjectFactory factory = new PoolableObjectFactorySample();

        // 2) 利用这个PoolableObjectFactory实例为参数,生成一个实现了ObjectPool接口的类(例如StackObjectPool)的实例,作为对象池。
        ObjectPool pool = new StackObjectPool(factory);

        try {
            for(long i = 0; i < 100 ; i++) {
                System.out.println("== " + i + " ==");

                // 3) 需要从对象池中取出对象时,调用该对象池的Object borrowObject()方法。
                obj = pool.borrowObject();

                System.out.println(obj);

                // 4) 需要将对象放回对象池中时,调用该对象池的void returnObject(Object obj)方法。
                pool.returnObject(obj);

            }
            obj = null;//明确地设为null,作为对象已归还的标志
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            try{
                if (obj != null) {//避免将一个对象归还两次
                    pool.returnObject(obj);
                }

                // 5) 当不再需要使用一个对象池时,调用该对象池的void close()方法,释放它所占据的资源。
                pool.close();

            }
            catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}

 

 ObjectPoolFactory
有时候,要在多处生成类型和设置都相同的ObjectPool。如果在每个地方都重写一次调用相应构造方法的代码,不但比较麻烦,而且日后修改起来,也有所不便。这种时候,正是使用ObjectPoolFactory的时机。 
ObjectPoolFactory是一个在org.apache.commons.pool中定义的接口,它定义了一个称为ObjectPool createPool()方法,可以用于大量生产类型和设置都相同的ObjectPool。 

 

public static void main(String[] args) {  
        Object obj = null;  
        PoolableObjectFactory factory = new PoolableObjectFactorySample();  
        ObjectPoolFactory poolFactory = new StackObjectPoolFactory(factory);  
        ObjectPool pool = poolFactory.createPool();  
        try {  
            for(long i = 0; i < 100 ; i++) {  
                System.out.println("== " + i + " ==");  
                obj = pool.borrowObject();  
                System.out.println(obj);  
                pool.returnObject(obj);  
            }  
            obj = null;  
        }  
        catch (Exception e) {  
            e.printStackTrace();  
        }  
        finally {  
            try{  
                if (obj != null) {  
                    pool.returnObject(obj);  
                }  
                pool.close();  
            }  
            catch (Exception e){  
                e.printStackTrace();  
            }  
        }  
    }  

 

 

 

结束语 
恰当地使用对象池化,可以有效地降低频繁生成某些对象所造成的开销,从而提高整体的性能。而借助Apache Commons Pool组件,可以有效地减少花在处理对象池化上的工作量,进而,向其它重要的工作里,投入更多的时间和精力。