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


    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组件,可以有效地减少花在处理对象池化上的工作量,进而,向其它重要的工作里,投入更多的时间和精力。 

  • 相关阅读:
    1.4(java学习笔记) 面向对象内存分析
    1.3(java学习笔记)构造方法及重载
    1.2(java学习笔记)类与对象
    1.1(java学习笔记) 面向过程与面向对象
    STM32F0库函数初始化系列:GPIO配置
    STM32F4库函数初始化系列:PWM输出
    STM32F4库函数初始化系列:DMA串口接收
    STM32F4库函数初始化系列:三重ADC——DMA
    STM32F1库函数初始化系列:DMA—ADC采集
    STM32F4库函数初始化系列:串口DMA接收
  • 原文地址:https://www.cnblogs.com/plf112233/p/6527902.html
Copyright © 2020-2023  润新知