Maison >Java >javaDidacticiel >Comment implémenter un pool d'objets en Java

Comment implémenter un pool d'objets en Java

WBOY
WBOYavant
2023-05-11 14:49:061305parcourir

1. Qu'est-ce qu'un pool d'objets ?

Le pooling n'est pas une nouvelle technologie. Il s'agit plutôt d'un modèle de conception logicielle. Sa fonction principale est de mettre en cache un ensemble d'objets initialisés à utiliser. À tout moment. Peut être utilisé. Dans la plupart des scénarios, les pools d'objets mettent en cache les objets trop coûteux à créer ou qui doivent être créés et utilisés de manière répétée. Le temps nécessaire pour récupérer un objet du pool est prévisible, mais le temps nécessaire pour créer un nouvel objet est incertain.

Lorsqu'un nouvel objet est nécessaire, un est emprunté au pool d'objets. Le pool d'objets marque ensuite que l'objet actuel est en cours d'utilisation et le renvoie au pool d'objets après utilisation afin qu'il puisse être prêté. encore.

Scénarios d'utilisation courants du pooling d'objets :

  • 1.

  • 2. La création fréquente d'un grand nombre d'objets en double générera beaucoup de fragmentation de la mémoire.

  • 3. Il n'y aura pas trop d'objets utilisés en même temps.

  • 4. Scénarios spécifiques courants tels que le pool de connexions à la base de données, le pool de threads, etc.

2. Pourquoi avez-vous besoin d'un pool d'objets La connexion prend trop de temps, notre processus de requête peut ressembler à ceci.

Requête 1 : Établir la connexion à la base de données -> Lancer la requête -> Recevoir la réponse -> Fermer la connexion

  • # 🎜🎜#Requête 2 : Établir une connexion à la base de données -> Lancer une requête -> Recevoir une réponse -> Fermer la connexion

  • Requête 3 : Établir une base de données connexion -> ; Initier la requête -> Recevoir la réponse -> Fermer la connexion

  • Dans ce mode, la connexion fermée doit être rétablie à chaque requête, la connexion est établie. Une opération qui prend du temps, ce mode affecte donc les performances globales du programme.

    Alors, qu'est-ce que ça fait d'utiliser la pensée mutualisée ? Le même processus se transforme en étapes suivantes.

Initialisation : établir N connexions à la base de données -> mettre en cache

  • Requête 1 : emprunter au cache Se connecter à la base de données -> Lancer la requête -> Recevoir la réponse -> Retourner l'objet de connexion à la base de données dans le cache

  • Requête 2 : Emprunter la connexion à la base de données à partir du cache -> ; -> Recevoir la réponse -> Renvoyez l'objet de connexion à la base de données dans le cache

  • Requête 3 : Emprunter la connexion à la base de données depuis le cache -> ; Renvoie l'objet de connexion à la base de données dans le cache

  • Après avoir utilisé l'idée de pooling, la connexion à la base de données ne sera pas fréquemment créée et fermée, mais sera fermée après son démarrage .N connexions sont initialisées pour une utilisation ultérieure et les objets sont renvoyés après utilisation, de sorte que les performances globales du programme soient améliorées.

    3. Mise en place du pool d'objets
A travers l'exemple ci-dessus, on retrouve également plusieurs étapes clés de l'idée de pooling :

Initialisation, prêt, retour#🎜 🎜#. Les étapes de

Destruction

ne sont pas présentées ci-dessus. Dans certains scénarios, le processus de destruction d'objet est également requis, comme la libération d'une connexion.

Implémentons manuellement un pool d'objets simple pour approfondir notre compréhension du pool d'objets. L'essentiel est de définir une classe de gestion de pool d'objets, puis d'implémenter l'initialisation , le prêt, le retour, la destruction de et d'autres opérations de l'objet qu'il contient.

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("已经销毁了所有对象");
    }
}
Le code est relativement simple, juste un exemple simple. Ci-dessous, nous montrons comment l'utiliser en regroupant un objet de connexion Redis Jedis.

En fait, Jedis dispose déjà de l'objet de gestion de pool Jedis correspondant, JedisPool. Cependant, afin de démontrer ici l'implémentation du pool d'objets, nous n'utiliserons pas le JedisPool officiellement fourni. Le démarrage d'un service Redis ne sera pas présenté ici. En supposant que vous disposez déjà d'un service Redis, ce qui suit présente les dépendances Maven nécessaires pour vous connecter à Redis en Java.

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

Comment utiliser les objets Jedis dans des circonstances normales :

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

Si vous utilisez le pool d'objets ci-dessus, vous pouvez l'utiliser comme suit.

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();
    }
}

Journal de sortie :

Objet ajouté :1556956098

Objet ajouté :1252585652

Objet de carême :1252585652# 🎜 🎜#redis obtenez :www.wdbyte.com

Objet retourné : 1252585652

Tous les objets ont été détruits
Il n'y a aucun objet qui peut être prêté


#🎜🎜 #Si vous utilisez JMH pour comparer les performances des requêtes Redis utilisant le pooling d'objets avec la méthode consistant à créer normalement une connexion Redis, puis à interroger pour fermer la connexion, vous constaterez que les performances des deux sont très différentes. Voici les résultats des tests. On peut constater que les performances après utilisation du pooling d'objets sont environ 5 fois supérieures à celles de la méthode sans pooling.

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. 开源的对象池工具

上面自己实现的对象池总归有些简陋了,其实开源工具中已经有了非常好用的对象池的实现,如 Apache 的 commons-pool2 工具,很多开源工具中的对象池都是基于此工具实现,下面介绍这个工具的使用方式。

maven 依赖:

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

在 commons-pool2 对象池工具中有几个关键的类。

  • • PooledObjectFactory 类是一个工厂接口,用于实现想要池化对象的创建、验证、销毁等操作。

  • • GenericObjectPool 类是一个通用的对象池管理类,可以进行对象的借出、归还等操作。

  • • GenericObjectPoolConfig 类是对象池的配置类,可以进行对象的最大、最小等容量信息进行配置。

下面通过一个具体的示例演示 commons-pool2 工具类的使用,这里依旧选择 Redis 连接对象 Jedis 作为演示。

实现 PooledObjectFactory 工厂类,实现其中的对象创建和销毁方法。

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;
    }
}

继承 GenericObjectPool 类,实现对对象的借出、归还等操作。

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);
    }
}

可以看到 MyGenericObjectPool 类的构造函数中的入参有 GenericObjectPoolConfig 对象,这是个对象池的配置对象,可以配置对象池的容量大小等信息,这里就不配置了,使用默认配置。

通过 GenericObjectPoolConfig 的源码可以看到默认配置中,对象池的容量是 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;

下面编写一个对象池使用测试类。

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();
    }

}

输出日志:

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

上面已经演示了 commons-pool2 工具中的对象池的使用方式,从上面的例子中可以发现这种对象池中只能存放同一种初始化条件的对象,如果这里的 Redis 我们需要存储一个本地连接和一个远程连接的两种 Jedis 对象,就不能满足了。那么怎么办呢?

其实 commons-pool2 工具已经考虑到了这种情况,通过增加一个 key 值可以在同一个对象池管理中进行区分,代码和上面类似,直接贴出完整的代码实现。

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);
    }
}

输出日志:

redis get :www.wdbyte.com

5. JedisPool 对象池实现分析

这篇文章中的演示都使用了 Jedis 连接对象,其实在 Jedis SDK 中已经实现了相应的对象池,也就是我们常用的 JedisPool 类。那么这里的 JedisPool 是怎么实现的呢?我们先看一下 JedisPool 的使用方式。

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();
    }
}

代码中添加了注释,可以看到通过 jedisPool.getResource() 拿到了一个对象,这里和上面 commons-pool2 工具中的 borrowObject 十分相似,继续追踪它的代码实现可以看到下面的代码。

// 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);
    }
}

竟然看到了 super.borrowObject() ,多么熟悉的方法,继续分析代码可以发现 Jedis 对象池也是使用了 commons-pool2 工具作为实现。既然如此,那么 jedis.close() 方法的逻辑我们应该也可以猜到了,应该有一个归还的操作,查看代码发现果然如此。

// 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);
        }
    }
}

通过上面的分析,可见 Jedis 确实使用了 commons-pool2 工具进行对象池的管理,通过分析 JedisPool 类的继承关系图也可以发现。

Comment implémenter un pool dobjets en Java

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer