池技术使用-commons-pool2

池技术

日常搬砖过程中对池技术的接触很多,最具代表的是连接池。
连接池也是一种池技术,本质上都是对象池。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,到期了就直接“还”回去了,因此这里的输出会将之前的也打印出来,虽然归还了,但是却没清理掉它的内容。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352