Heim > Java > javaLernprogramm > Hauptteil

So implementieren Sie einen Objektpool in Java

WBOY
Freigeben: 2023-05-11 14:49:06
nach vorne
1283 Leute haben es durchsucht

1. Was ist ein Objektpool? In den meisten Szenarien speichern Objektpools Objekte, deren Erstellung zu teuer ist oder die wiederholt erstellt und verwendet werden müssen. Die Zeit zum Abrufen eines Objekts aus dem Pool ist vorhersehbar, die Zeit zum Erstellen eines neuen Objekts jedoch ungewiss.

Wenn ein neues Objekt benötigt wird, wird eines aus dem Pool ausgeliehen. Der Objektpool markiert dann, dass das aktuelle Objekt verwendet wird, und gibt es nach der Verwendung an den Objektpool zurück, damit es ausgeliehen werden kann wieder.

Häufige Nutzungsszenarien des Objektpoolings:

    1. Die Kosten für die Objekterstellung sind zu hoch.
  • 2. Das häufige Erstellen einer großen Anzahl doppelter Objekte führt zu einer starken Speicherfragmentierung.
  • 3 Es werden nicht zu viele Objekte gleichzeitig verwendet.
  • 4. Häufige spezifische Szenarien wie Datenbankverbindungspool, Threadpool usw.
  • 2. Warum benötigen Sie einen Objektpool? Ohne den Einsatz der Pooling-Technologie könnte unser Abfrageprozess so aussehen.

Abfrage 1: Datenbankverbindung herstellen -> Abfrage einleiten -> Verbindung schließen 🎜🎜#Abfrage 2: Datenbankverbindung herstellen -> Eine Abfrage initiieren -> Verbindung schließen

  • Abfrage 3: Eine Datenbank erstellen Verbindung -> ;Anfrage einleiten -> Verbindung schließen

  • In diesem Modus muss die geschlossene Verbindung bei jeder Abfrage neu aufgebaut werden Die Verbindung wird hergestellt. Ein zeitaufwändiger Vorgang, daher wirkt sich dieser Modus auf die Gesamtleistung des Programms aus.

    Wie ist es also, Pooling-Denken zu nutzen? Der gleiche Prozess wandelt sich in die folgenden Schritte um.
  • Initialisierung: N Datenbankverbindungen herstellen -> Zwischenspeichern

Abfrage 1: Aus Cache ausleihen Connect zur Datenbank -> Abfrage einleiten -> Datenbankverbindungsobjekt in den Cache zurückgeben

    Abfrage 2: Datenbankverbindung aus dem Cache ausleihen -> -> Antwort empfangen -> Datenbankverbindungsobjekt in den Cache zurückgeben
  • Abfrage 3: Datenbankverbindung aus dem Cache ausleihen -> Antwort erhalten ; Geben Sie das Datenbankverbindungsobjekt in den Cache zurück.
  • Nach der Verwendung der Pooling-Idee wird die Datenbankverbindung nicht häufig erstellt und geschlossen, sondern nach dem Start geschlossen .N Verbindungen werden für die spätere Verwendung initialisiert und die Objekte werden nach der Verwendung zurückgegeben, sodass die Gesamtleistung des Programms verbessert wird.
  • 3. Implementierung des Objektpools

  • Anhand des obigen Beispiels können wir auch mehrere Schlüsselschritte der Pooling-Idee finden:
  • Initialisierung, Ausleihe, Rückgabe#🎜 🎜#. Der Schritt

    Zerstörung

    wird oben nicht angezeigt. In einigen Szenarien ist auch der Objektzerstörungsprozess erforderlich, beispielsweise das Lösen einer Verbindung.
Lassen Sie uns einen einfachen Objektpool manuell implementieren, um unser Verständnis des Objektpools zu vertiefen. Die Hauptsache besteht darin, eine Objektpool-Verwaltungsklasse zu definieren und dann

Initialisierung, Verleihung, Rückgabe, Zerstörung

und andere Vorgänge des darin enthaltenen Objekts zu implementieren.

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("已经销毁了所有对象");
    }
}
Nach dem Login kopieren

Der Code ist relativ einfach, nur ein einfaches Beispiel. Nachfolgend zeigen wir, wie man ihn durch Pooling eines Redis-Verbindungsobjekts Jedis verwendet.

Tatsächlich verfügt Jedis bereits über das entsprechende Jedis-Pool-Verwaltungsobjekt JedisPool. Um die Implementierung des Objektpools hier zu demonstrieren, werden wir jedoch nicht den offiziell bereitgestellten JedisPool verwenden. Das Starten eines Redis-Dienstes wird hier nicht vorgestellt. Unter der Annahme, dass Sie bereits über einen Redis-Dienst verfügen, werden im Folgenden die Maven-Abhängigkeiten vorgestellt, die für die Verbindung mit Redis in Java erforderlich sind.

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.2.0</version>
</dependency>
Nach dem Login kopieren

So verwenden Sie Jedis-Objekte unter normalen Umständen:

Jedis jedis = new Jedis("localhost", 6379);
String name = jedis.get("name");
System.out.println(name);
jedis.close();
Nach dem Login kopieren
Wenn Sie den oben genannten Objektpool verwenden, können Sie ihn wie folgt verwenden. Nr. 🎜 🎜#redis get :www.wdbyte.comZurückgegebenes Objekt: 1252585652

Alle Objekte wurden zerstört

Es gibt keine Objekte, die ausgeliehen werden können

#🎜🎜 #Wenn Sie verwenden Wenn JMH die Leistung der Redis-Abfrage mithilfe von Objektpooling mit der Methode vergleicht, bei der normalerweise eine Redis-Verbindung erstellt und dann abgefragt wird, um die Verbindung zu schließen, werden Sie feststellen, dass die Leistung der beiden sehr unterschiedlich ist. Im Folgenden sind die Testergebnisse aufgeführt. Es kann festgestellt werden, dass die Leistung nach der Verwendung von Objektpooling etwa fünfmal so hoch ist wie die der Nicht-Pooling-Methode.

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>
Nach dem Login kopieren

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;
    }
}
Nach dem Login kopieren

继承 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);
    }
}
Nach dem Login kopieren

可以看到 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;
Nach dem Login kopieren

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

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

}
Nach dem Login kopieren

输出日志:

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);
    }
}
Nach dem Login kopieren

输出日志:

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();
    }
}
Nach dem Login kopieren

代码中添加了注释,可以看到通过 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);
    }
}
Nach dem Login kopieren

竟然看到了 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);
        }
    }
}
Nach dem Login kopieren

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

So implementieren Sie einen Objektpool in Java

Das obige ist der detaillierte Inhalt vonSo implementieren Sie einen Objektpool in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:yisu.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage