美文网首页
池技术使用-commons-pool2

池技术使用-commons-pool2

作者: MR丿VINCENT | 来源:发表于2020-07-06 16:28 被阅读0次

    池技术

    日常搬砖过程中对池技术的接触很多,最具代表的是连接池。
    连接池也是一种池技术,本质上都是对象池。commons-pool是apacha基金会开源的一款常见的对象池工具库。

    使用池化主要是为了节省对象创建的开销。比如日常开发息息相关的数据源连接池,就是为了减少连接创建的时间而生的。可以简单评估一下一个连接的创建经历哪些操作:对象创建,tcp连接等。tcp连接又得经历三次握手,如果是tls/ssl还得做证书签名验证,想想都麻烦。所以使用连接池可以减少这些消耗性能的操作,把机器更多的性能留给业务。

    快速上手

    这里直接搬运官网的demo。

    下面是一个从流中读取字符串的工具类。

    import java.io.Reader; 
    import java.io.IOException; 
     
    public class ReaderUtil { 
        public ReaderUtil() { 
        } 
     
        /** 
         * Dumps the contents of the {@link Reader} to a 
         * String, closing the {@link Reader} when done. 
         */ 
        public String readToString(Reader in) throws IOException { 
            StringBuffer buf = new StringBuffer(); 
            try { 
                for(int c = in.read(); c != -1; c = in.read()) { 
                    buf.append((char)c); 
                } 
                return buf.toString(); 
            } catch(IOException e) { 
                throw e; 
            } finally { 
                try { 
                    in.close(); 
                } catch(Exception e) { 
                    // ignored 
                } 
            } 
        } 
    }
    

    咋看上去没什么毛病,我们在日常搬砖中也会写出这样的工具类,也可以很好的工作。为了突出说明池化技术的优点,这个工具类还能继续优化,虽然优化空间不是很大。

    import java.io.IOException;
    import java.io.Reader;
    import org.apache.commons.pool2.ObjectPool;
    
    public class ReaderUtil {
        
        private ObjectPool<StringBuffer> pool;
        
        public ReaderUtil(ObjectPool<StringBuffer> pool) {
            this.pool = pool;
        }
    
        /**
         * Dumps the contents of the {@link Reader} to a String, closing the {@link Reader} when done.
         */
        public String readToString(Reader in)
            throws IOException {
            StringBuffer buf = null;
            try {
                buf = pool.borrowObject();
                for (int c = in.read(); c != -1; c = in.read()) {
                    buf.append((char) c);
                }
                return buf.toString();
            } catch (IOException e) {
                throw e;
            } catch (Exception e) {
                throw new RuntimeException("Unable to borrow buffer from pool" + e.toString());
            } finally {
                try {
                    in.close();
                } catch (Exception e) {
                    // ignored
                }
                try {
                    if (null != buf) {
                        pool.returnObject(buf);
                    }
                } catch (Exception e) {
                    // ignored
                }
            }
        }
    }
    

    明眼人很快就能看出区别,无非就是将StringBuffer的创建方式做了变化,以前是直接new,每次调用都得new一下,现在是通过向pool借。

    import org.apache.commons.pool2.BasePooledObjectFactory;
    import org.apache.commons.pool2.PooledObject;
    import org.apache.commons.pool2.impl.DefaultPooledObject;
    
    public class StringBufferFactory
        extends BasePooledObjectFactory<StringBuffer> {
    
        @Override
        public StringBuffer create() {
            return new StringBuffer();
        }
    
        /**
         * Use the default PooledObject implementation.
         */
        @Override
        public PooledObject<StringBuffer> wrap(StringBuffer buffer) {
            return new DefaultPooledObject<StringBuffer>(buffer);
        }
    
        /**
         * When an object is returned to the pool, clear the buffer.
         */
        @Override
        public void passivateObject(PooledObject<StringBuffer> pooledObject) {
            pooledObject.getObject().setLength(0);
        }
    
        // for all other methods, the no-op implementation
        // in BasePooledObjectFactory will suffice
    }
    

    最终只需要将pool传给这个util:

    ReaderUtil readerUtil = new ReaderUtil(new GenericObjectPool<StringBuffer>(new StringBufferFactory()));
    

    需要开发关注的仅仅是对象工厂StringBufferFactory的实现,在这个工厂中,主要任务是创建对象,也就是最开始的new对象。把对象的创建工作转移到了工厂里,而不是硬生生的new出来,这也是设计模式的一种体现。

    官网给的这个例子非常简洁易懂,很容易快速上手。然而其中还有很多配置参数,能让对象池功能更加丰富。

    带配置参数的入门

    public void test1() throws InterruptedException {
            // 创建池对象工厂
            PooledObjectFactory<StringBuilder> factory = new MyPoolableObjectFactory();
    
            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            // 最大空闲数
            poolConfig.setMaxIdle(5);
            // 最小空闲数, 池中只有一个空闲对象的时候,池会在创建一个对象,并借出一个对象,从而保证池中最小空闲数为1
            poolConfig.setMinIdle(1);
            // 最大池对象总数
            poolConfig.setMaxTotal(20);
            // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
            poolConfig.setMinEvictableIdleTimeMillis(1800000);
            // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
            poolConfig.setTimeBetweenEvictionRunsMillis(1800000 * 2L);
            // 在获取对象的时候检查有效性, 默认false
            poolConfig.setTestOnBorrow(true);
            // 在归还对象的时候检查有效性, 默认false
            poolConfig.setTestOnReturn(false);
            // 在空闲时检查有效性, 默认false
            poolConfig.setTestWhileIdle(false);
            // 最大等待时间, 默认的值为-1,表示无限等待。
            poolConfig.setMaxWaitMillis(6000);
            // 是否启用后进先出, 默认true
            poolConfig.setLifo(true);
            // 连接耗尽时是否阻塞, false报异常,true阻塞直到超时, 默认true
            poolConfig.setBlockWhenExhausted(true);
            // 每次逐出检查时 逐出的最大数目 默认3
            poolConfig.setNumTestsPerEvictionRun(3);
    
            CountDownLatch latch = new CountDownLatch(40);
            // 创建对象池
            final GenericObjectPool<StringBuilder> pool = new GenericObjectPool<StringBuilder>(factory, poolConfig);
            for (int i = 0; i < 40; i++) {
                int finalI = i;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        StringBuilder resource = null;
                        try {
                            // 注意,如果对象池没有空余的对象,那么这里会block,可以设置block的超时时间
                            resource = pool.borrowObject();
                            resource.append("+").append(finalI);
                            System.out.println(resource);
                            Thread.sleep(2000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            // 申请的资源用完了记得归还,不然其他人要申请时可能就没有资源用了
                            pool.returnObject(resource);
                            latch.countDown();
                        }
                    }
                }).start();
    
            }
            latch.await();
    
            System.out.println("=====finish====");
        }
        
        private static class MyPoolableObjectFactory extends BasePooledObjectFactory<StringBuilder> {
    
            @Override
            public StringBuilder create() throws Exception {
                return new StringBuilder();
            }
    
            @Override
            public PooledObject<StringBuilder> wrap(StringBuilder obj) {
                return new DefaultPooledObject<>(obj);
            }
        }
    

    这个demo中给了很多配置参数,注释中写的都很明白。值得注意的是这个demo中输出的结果可能不一致。因为多线程的缘故。

    下面是其中的一种输出结果:

    +5
    +9
    +8
    +11
    +12
    +10
    +4
    +1
    +3
    +2
    +7
    +13
    +15
    +16
    +6
    +17
    +18
    +19
    +0
    +14
    +3+20
    +9+21
    +11+22
    +4+25
    +2+23
    +5+24
    +1+26
    +0+27
    +14+28
    +9+21+29
    +3+20+30
    +4+25+31
    +1+26+32
    +2+23+33
    +11+22+35
    +5+24+34
    +0+27+36
    +14+28+37
    +3+20+30+38
    +9+21+29+39
    =====finish====
    

    这里开了40个线程去获取对象,通过使用latch使得所有线程都结束后再结束主线程。
    这个latch得控制为40,因为每个线程跑完都得减一,直到为0后表示所有线程都结束。这里都latch只是用于控制先后顺序,也就是即使主线程结束了,子线程也能继续执行下去,除非子线程都是守护线程。

    由于设置都最大数量为20,因此会有20个线程先获取到stringbuffer对象,然后这里睡眠了2秒钟,模拟一下对这个对象的使用,剩下的20个线程会尝试去“借”对象,但是之前的20个线程还没用完,因此不会马上获取到,这里设置了一个超时时间6s,也就是最多等6s,如果6s后还是没能等到,那就直接抛异常了。因为模拟只使用2s,到期了就直接“还”回去了,因此这里的输出会将之前的也打印出来,虽然归还了,但是却没清理掉它的内容。

    仔细来看,带参数的也就不过如此,对于开发者而言也没有什么太复杂的地方,十分容易上手。接下来就拨开云雾,仔细瞧瞧池技术是如何实现的。

    相关文章

      网友评论

          本文标题:池技术使用-commons-pool2

          本文链接:https://www.haomeiwen.com/subject/hdqgqktx.html