1

Like and watch again, the power is unlimited. Search " Program Ape Alang " on WeChat.

This article Github.com/niumoo/JavaNotes and the unread code blog have been included, there are many knowledge points and series of articles.

Recently, when analyzing the time consumption of an interface in an application, I found that a seemingly ordinary object creation operation takes about 8ms each time. After analysis, it is found that this object can be optimized through the object pool mode. This step takes only 0.01ms. This article introduces the knowledge of object pools.

对象初始化耗时 8ms

1. What is an object pool

Pooling is not a new technology, it is more like a software design pattern, the main function is to cache a set of initialized objects, ready to use. In most scenarios, the object pool caches objects that are too expensive to create or need to be repeatedly created and used. The time to fetch an object from the pool is predictable, but the time to create a new object is uncertain.

When a new object is needed, one is lent to the pool, and then the object pool marks the current object as being in use, and returns it to the object pool after use, so that it can be lent out again.

Common use of object pooling scenarios:

  1. Object creation costs are too high.
  2. A large number of duplicate objects need to be created frequently, resulting in a lot of memory fragmentation.
  3. Not too many objects are used at the same time.
  4. Common specific scenarios such as database connection pool, thread pool, etc.

2. Why do we need object pooling?

If the creation cost of an object is high , such as establishing a connection to the database takes too long, our query process may look like this without using pooling technology.

 查询 1:建立数据库连接 -> 发起查询 -> 收到响应 -> 关闭连接
查询 2:建立数据库连接 -> 发起查询 -> 收到响应 -> 关闭连接
查询 3:建立数据库连接 -> 发起查询 -> 收到响应 -> 关闭连接

In this mode, each query has to re-establish and close the connection, because establishing a connection is a time-consuming operation, so this mode will affect the overall performance of the program.

So what is it like to use the pooling idea? The same process translates to the following steps.

 初始化:建立 N 个数据库连接 -> 缓存起来
查询 1:从缓存借到数据库连接 -> 发起查询 -> 收到响应 -> 归还数据库连接对象到缓存
查询 2:从缓存借到数据库连接 -> 发起查询 -> 收到响应 -> 归还数据库连接对象到缓存
查询 3:从缓存借到数据库连接 -> 发起查询 -> 收到响应 -> 归还数据库连接对象到缓存

After using the pooling idea, database connections are not created and closed frequently, but N connections are initialized for subsequent use after startup, and the objects are returned after use, so that the overall performance of the program is improved.

3. Implementation of object pool

Through the above example, several key steps of the pooling idea can also be found: initialization, lending, and returning . The destruction steps are not shown above. In some scenarios, the process of object destruction is also required, such as releasing the connection.

Next, we manually implement a simple object pool to deepen our understanding of the object pool. It is mainly to set an object pool management class, and then implement the initialization, loan, return, destruction and other operations of objects in it.

 package com.wdbyet.tool.objectpool.mypool;

import java.io.Closeable;
import java.io.IOException;
import java.util.HashSet;
import java.util.Stack;

/**
 * @author https://www.wdbyte.com
 */
public class MyObjectPool<T extends Closeable> {

    // 池子大小
    private Integer size = 5;
    // 对象池栈。后进先出
    private Stack<T> stackPool = new Stack<>();
    // 借出的对象的 hashCode 集合
    private HashSet<Integer> borrowHashCodeSet = new HashSet<>();

    /**
     * 增加一个对象
     *
     * @param t
     */
    public synchronized void addObj(T t) {
        if ((stackPool.size() + borrowHashCodeSet.size()) == size) {
            throw new RuntimeException("池中对象已经达到最大值");
        }
        stackPool.add(t);
        System.out.println("添加了对象:" + t.hashCode());
    }

    /**
     * 借出一个对象
     *
     * @return
     */
    public synchronized T borrowObj() {
        if (stackPool.isEmpty()) {
            System.out.println("没有可以被借出的对象");
            return null;
        }
        T pop = stackPool.pop();
        borrowHashCodeSet.add(pop.hashCode());
        System.out.println("借出了对象:" + pop.hashCode());
        return pop;
    }

    /**
     * 归还一个对象
     *
     * @param t
     */
    public synchronized void returnObj(T t) {
        if (borrowHashCodeSet.contains(t.hashCode())) {
            stackPool.add(t);
            borrowHashCodeSet.remove(t.hashCode());
            System.out.println("归还了对象:" + t.hashCode());
            return;
        }
        throw new RuntimeException("只能归还从池中借出的对象");
    }

    /**
     * 销毁池中对象
     */
    public synchronized void destory() {
        if (!borrowHashCodeSet.isEmpty()) {
            throw new RuntimeException("尚有未归还的对象,不能关闭所有对象");
        }
        while (!stackPool.isEmpty()) {
            T pop = stackPool.pop();
            try {
                pop.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("已经销毁了所有对象");
    }
}

The code is relatively simple, just a simple example, let's demonstrate how to use it by pooling a Redis connection object Jedis.

In fact, Jedis already has the corresponding Jedis pool management object, the JedisPool, but here we will not use the officially provided JedisPool in order to demonstrate the implementation of the object pool.

Starting a Redis service will not be introduced here. Assuming you already have a Redis service, the following introduces the Maven dependencies needed to connect to Redis in Java.

 <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.2.0</version>
</dependency>

How the Jedis object is used under normal circumstances:

 Jedis jedis = new Jedis("localhost", 6379);
String name = jedis.get("name");
System.out.println(name);
jedis.close();

If you use the object pool above, you can use it like the following.

 package com.wdbyet.tool.objectpool.mypool;

import redis.clients.jedis.Jedis;

/**
 * @author niulang
 * @date 2022/07/02
 */
public class MyObjectPoolTest {

    public static void main(String[] args) {
        MyObjectPool<Jedis> objectPool = new MyObjectPool<>();
        // 增加一个 jedis 连接对象
        objectPool.addObj(new Jedis("127.0.0.1", 6379));
        objectPool.addObj(new Jedis("127.0.0.1", 6379));
        // 从对象池中借出一个 jedis 对象
        Jedis jedis = objectPool.borrowObj();
        // 一次 redis 查询
        String name = jedis.get("name");
        System.out.println(String.format("redis get:" + name));
        // 归还 redis 连接对象
        objectPool.returnObj(jedis);
        // 销毁对象池中的所有对象
        objectPool.destory();
        // 再次借用对象
        objectPool.borrowObj();
    }
}

Output log:

 添加了对象:1556956098
添加了对象:1252585652
借出了对象:1252585652
redis get:www.wdbyte.com
归还了对象:1252585652
已经销毁了所有对象
没有可以被借出的对象

If you use JMH to compare the performance of Redis query using object pooling with the normal way of creating a Redis connection and then querying to close the connection, you will find that the performance of the two is very different. The following are the test results. It can be found that the performance of using object pooling is about 5 times that of the non-pooling method.

 Benchmark                   Mode  Cnt      Score       Error  Units
MyObjectPoolTest.test      thrpt   15   2612.689 ±   358.767  ops/s
MyObjectPoolTest.testPool  thrpt    9  12414.228 ± 11669.484  ops/s

4. Open source object pool tool

The object pool implemented by myself above is always a bit rudimentary. In fact, there are already very useful object pool implementations in open source tools, such as Apache's commons-pool2 tool. The object pools in many open source tools are based on this. Tool implementation, the following describes how to use this tool.

maven dependencies:

 <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

There are several key classes in the commons-pool2 object pooling tool.

  • PooledObjectFactory The class is a factory interface for creating, validating, and destroying objects that you want to pool.
  • GenericObjectPool The class is a general object pool management class, which can perform operations such as lending and returning objects.
  • GenericObjectPoolConfig The class is the configuration class of the object pool, which can configure the maximum and minimum capacity information of the object.

The following is a specific example to demonstrate the use of the tool class commons-pool2 the Redis connection object Jedis is still selected as the demonstration.

Implement PooledObjectFactory factory class, and implement the object creation and destruction methods in it.

 public class MyPooledObjectFactory implements PooledObjectFactory<Jedis> {

    @Override
    public void activateObject(PooledObject<Jedis> pooledObject) throws Exception {

    }

    @Override
    public void destroyObject(PooledObject<Jedis> pooledObject) throws Exception {
        Jedis jedis = pooledObject.getObject();
        jedis.close();
          System.out.println("释放连接");
    }

    @Override
    public PooledObject<Jedis> makeObject() throws Exception {
        return new DefaultPooledObject(new Jedis("localhost", 6379));
    }

    @Override
    public void passivateObject(PooledObject<Jedis> pooledObject) throws Exception {
    }

    @Override
    public boolean validateObject(PooledObject<Jedis> pooledObject) {
        return false;
    }
}

Inheriting the GenericObjectPool class to implement operations such as lending and returning objects.

 public class MyGenericObjectPool extends GenericObjectPool<Jedis> {

    public MyGenericObjectPool(PooledObjectFactory factory) {
        super(factory);
    }

    public MyGenericObjectPool(PooledObjectFactory factory, GenericObjectPoolConfig config) {
        super(factory, config);
    }

    public MyGenericObjectPool(PooledObjectFactory factory, GenericObjectPoolConfig config,
        AbandonedConfig abandonedConfig) {
        super(factory, config, abandonedConfig);
    }
}

You can see that the MyGenericObjectPool class constructor has GenericObjectPoolConfig object, which is a configuration object of the object pool, which can configure the capacity and size of the object pool and other information, which will not be configured here. , using the default configuration.

Through the source code of GenericObjectPoolConfig , you can see that in the default configuration, the capacity of the object pool is 8 .

 public class GenericObjectPoolConfig<T> extends BaseObjectPoolConfig<T> {

    /**
     * The default value for the {@code maxTotal} configuration attribute.
     * @see GenericObjectPool#getMaxTotal()
     */
    public static final int DEFAULT_MAX_TOTAL = 8;

    /**
     * The default value for the {@code maxIdle} configuration attribute.
     * @see GenericObjectPool#getMaxIdle()
     */
    public static final int DEFAULT_MAX_IDLE = 8;

Let's write an object pool using the test class.

 public class ApachePool {

    public static void main(String[] args) throws Exception {
        MyGenericObjectPool objectMyObjectPool = new MyGenericObjectPool(new MyPooledObjectFactory());
        Jedis jedis = objectMyObjectPool.borrowObject();
        String name = jedis.get("name");
        System.out.println(name);
        objectMyObjectPool.returnObject(jedis);
        objectMyObjectPool.close();
    }

}

Output log:

 redis get:www.wdbyte.com
释放连接

The above has demonstrated the use of the object pool in the commons-pool2 tool. From the above example, it can be found that only objects of the same initialization condition can be stored in this object pool. If Redis here, we need to store a Two Jedis objects of a local connection and a remote connection cannot be satisfied. So what to do?

In fact, commons-pool2 the tool has considered this situation, and can be distinguished in the same object pool management by adding a key value. The code is similar to the above, and the complete code implementation is directly posted.

 package com.wdbyet.tool.objectpool.apachekeyedpool;

import org.apache.commons.pool2.BaseKeyedPooledObjectFactory;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import redis.clients.jedis.Jedis;

/**
 * @author https://www.wdbyte.com
 * @date 2022/07/07
 */
public class ApacheKeyedPool {

    public static void main(String[] args) throws Exception {
        String key = "local";
        MyGenericKeyedObjectPool objectMyObjectPool = new MyGenericKeyedObjectPool(new MyKeyedPooledObjectFactory());
        Jedis jedis = objectMyObjectPool.borrowObject(key);
        String name = jedis.get("name");
        System.out.println("redis get :" + name);
        objectMyObjectPool.returnObject(key, jedis);
    }
}

class MyKeyedPooledObjectFactory extends BaseKeyedPooledObjectFactory<String, Jedis> {

    @Override
    public Jedis create(String key) throws Exception {
        if ("local".equals(key)) {
            return new Jedis("localhost", 6379);
        }
        if ("remote".equals(key)) {
            return new Jedis("192.168.0.105", 6379);
        }
        return null;
    }

    @Override
    public PooledObject<Jedis> wrap(Jedis value) {
        return new DefaultPooledObject<>(value);
    }
}

class MyGenericKeyedObjectPool extends GenericKeyedObjectPool<String, Jedis> {

    public MyGenericKeyedObjectPool(KeyedPooledObjectFactory<String, Jedis> factory) {
        super(factory);
    }

    public MyGenericKeyedObjectPool(KeyedPooledObjectFactory<String, Jedis> factory,
        GenericKeyedObjectPoolConfig<Jedis> config) {
        super(factory, config);
    }

    public MyGenericKeyedObjectPool(KeyedPooledObjectFactory<String, Jedis> factory,
        GenericKeyedObjectPoolConfig<Jedis> config, AbandonedConfig abandonedConfig) {
        super(factory, config, abandonedConfig);
    }
}

Output log:

 redis get :www.wdbyte.com

5. Analysis of JedisPool object pool implementation

The demonstrations in this article all use the Jedis connection object. In fact, the corresponding object pool has been implemented in the Jedis SDK, which is our commonly used JedisPool class. So how is the JedisPool implemented here? Let's first look at how JedisPool is used.

 package com.wdbyet.tool.objectpool;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * @author https://www.wdbyte.com
 */
public class JedisPoolTest {

    public static void main(String[] args) {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        // 从对象池中借一个对象
        Jedis jedis = jedisPool.getResource();
        String name = jedis.get("name");
        System.out.println("redis get :" + name);
        jedis.close();
        // 彻底退出前,关闭 Redis 连接池
        jedisPool.close();
    }
}

Comments have been added to the code, you can see that an object was obtained through jedisPool.getResource() , which is very similar to the above commons-pool2 tool borrowObject 7a73558ece60cf330a3ab2db38e99f73--- in the tool, continue to track its code The implementation can be seen in the code below.

 // redis.clients.jedis.JedisPool
// public class JedisPool extends Pool<Jedis> {
public Jedis getResource() {
    Jedis jedis = (Jedis)super.getResource();
    jedis.setDataSource(this);
    return jedis;
}
// 继续追踪 super.getResource()
// redis.clients.jedis.util.Pool
public T getResource() {
    try {
        return super.borrowObject();
    } catch (JedisException var2) {
        throw var2;
    } catch (Exception var3) {
        throw new JedisException("Could not get a resource from the pool", var3);
    }
}

I actually saw super.borrowObject() , what a familiar method, continue to analyze the code and find that the Jedis object pool is also applicable commons-pool2 tool as the implementation. In this case, we should also be able to guess the logic of the jedis.close() method. There should be a return operation. Check the code and find that it is.

 // redis.clients.jedis.JedisPool
// public class JedisPool extends Pool<Jedis> {
public void close() {
    if (this.dataSource != null) {
        Pool<Jedis> pool = this.dataSource;
        this.dataSource = null;
        if (this.isBroken()) {
            pool.returnBrokenResource(this);
        } else {
            pool.returnResource(this);
        }
    } else {
        this.connection.close();
    }
}
// 继续追踪 super.getResource()
// redis.clients.jedis.util.Pool
public void returnResource(T resource) {
    if (resource != null) {
        try {
            super.returnObject(resource);
        } catch (RuntimeException var3) {
            throw new JedisException("Could not return the resource to the pool", var3);
        }
    }
}

Through the above analysis, it can be seen that Jedis does use the commons-pool2 tool to manage the object pool, which can also be found by analyzing the inheritance diagram of the JedisPool class.

JedisPool 继承关系

6. Object Pool Summary

Through the introduction of this article, it can be found that the pooling idea has several obvious advantages.

  1. Can significantly improve application performance.
  2. If an object is too expensive to create, then using pooling is very effective.
  3. Pooling provides a way to manage and reuse objects, reducing memory fragmentation.
  4. You can provide a limit on the number of objects that can be created, and provide protection for scenes where some objects cannot be created too much.

However, there are some caveats to using object pooling, such as returning an object to ensure that the object has been reset to a reusable state. At the same time, it should also be noted that when using pooling, the size of the pool should be reasonably set according to the specific scene.

As always, the code in the article is hosted at Github.com/niumoo/javaNotes .

<end>

The article is continuously updated, you can search " Program Ape Alang " on WeChat or visit " Program Ape Alang Blog " to read it for the first time. This article has been included in Github.com/niumoo/JavaNotes . There are many knowledge points and series of articles. Welcome to Star.

<img width="400px" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c2baa841489f4e659c8cd039deffd797~tplv-k3u1fbpfcp-zoom-1.image">


程序猿阿朗
376 声望1.8k 粉丝

Hello world :)