Rumah >Java >javaTutorial >Bagaimana untuk melaksanakan kolam objek di Jawa

Bagaimana untuk melaksanakan kolam objek di Jawa

WBOY
WBOYke hadapan
2023-05-11 14:49:061331semak imbas

1. Apakah kumpulan objek?

Pengumpulan bukanlah teknologi baharu. Fungsi utamanya adalah untuk menyimpan satu set objek yang dimulakan pada bila-bila masa. Dalam kebanyakan senario, objek mengumpulkan objek cache yang terlalu mahal untuk dibuat atau perlu dibuat dan digunakan berulang kali Masa untuk mendapatkan semula objek daripada kolam boleh diramal, tetapi masa untuk mencipta objek baharu tidak pasti.

Apabila objek baharu diperlukan, satu dipinjam dari kolam objek kemudian menandakan objek semasa sedang digunakan dan mengembalikannya ke kolam objek selepas digunakan supaya ia boleh dipinjamkan semula.

Senario penggunaan biasa pengumpulan objek:

  • Kos penciptaan objek terlalu tinggi.

  • 2. Kerap mencipta sejumlah besar objek pendua akan menghasilkan banyak pemecahan memori.

  • 3. Tidak akan ada terlalu banyak objek yang digunakan pada masa yang sama.

  • 4. Senario khusus biasa seperti kumpulan sambungan pangkalan data, kumpulan benang, dll.

2. Mengapa kumpulan objek diperlukan

Jika kos penciptaan objek adalah sangat tinggi, contohnya, ia mengambil masa terlalu lama untuk mewujudkan sambungan pangkalan data, Tanpa menggunakan teknologi pengumpulan, proses pertanyaan kami mungkin kelihatan seperti ini.

  • Pertanyaan 1: Wujudkan sambungan pangkalan data -> Mulakan pertanyaan -> sambungan -> Mulakan pertanyaan -> Terima jawapan -> 🎜>

  • Dalam mod ini, sambungan tertutup mesti diwujudkan semula untuk setiap pertanyaan Kerana mewujudkan sambungan adalah operasi yang memakan masa, mod ini akan menjejaskan prestasi keseluruhan program.

    Jadi bagaimana rasanya menggunakan idea pengumpulan? Proses yang sama berubah menjadi langkah-langkah berikut.
  • Permulaan: Wujudkan N sambungan pangkalan data -> Cache mereka

Pertanyaan 1: Pinjam sambungan pangkalan data daripada cache -> ; Terima respons -> Kembalikan objek sambungan pangkalan data ke cache

  • Pertanyaan 2: Pinjam sambungan pangkalan data dari cache -> ke cache

  • Pertanyaan 3: Pinjam sambungan pangkalan data daripada cache -> Terima respons -> 🎜>

    Selepas menggunakan idea pengumpulan, sambungan pangkalan data tidak akan kerap dibuat dan ditutup Sebaliknya, sambungan N akan dimulakan untuk kegunaan seterusnya selepas permulaan, dan objek akan dikembalikan selepas digunakan. keseluruhan program Prestasi dipertingkatkan.
  • 3. Pelaksanaan kumpulan objek

    Melalui contoh di atas, kita juga boleh menemui beberapa langkah utama idea pengumpulan:
  • Permulaan, peminjaman, pulangan
  • . Langkah

    musnah

    tidak ditunjukkan di atas Dalam sesetengah senario, proses pemusnahan objek juga diperlukan, seperti melepaskan sambungan.
Mari kita laksanakan kumpulan objek ringkas secara manual untuk memperdalam pemahaman kita tentang kumpulan objek. Perkara utama ialah mentakrifkan kelas pengurusan kolam objek, dan kemudian melaksanakan

pemula, meminjamkan, memulangkan, memusnahkan

dan operasi lain objek di dalamnya.

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

Kod ini agak mudah, hanya contoh mudah di bawah kami menunjukkan cara menggunakannya dengan menggabungkan objek sambungan Redis Jedis.

Malah, Jedis sudah mempunyai objek pengurusan kolam Jedis yang sepadan iaitu JedisPool Walau bagaimanapun, untuk menunjukkan pelaksanaan kumpulan objek, kami tidak akan menggunakan JedisPool yang disediakan secara rasmi. Memulakan perkhidmatan Redis tidak diperkenalkan di sini dengan mengandaikan bahawa anda sudah mempunyai perkhidmatan Redis, yang berikut memperkenalkan kebergantungan Maven yang diperlukan untuk menyambung ke Redis di Java.

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

Cara menggunakan objek Jedis dalam keadaan biasa:

Jedis jedis = new Jedis("localhost", 6379);
String name = jedis.get("name");
System.out.println(name);
jedis.close();
Jika anda menggunakan kumpulan objek di atas, anda boleh menggunakannya seperti berikut.
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();
    }
}

Log keluaran:

Objek tambahan:1556956098

Objek tambah:1252585652

Objek dipinjamkan:1252585652

redisbyte:www.comd

Objek yang dikembalikan: 1252585652

Semua objek telah dimusnahkan

Tiada objek boleh dipinjamkan

Jika menggunakan JMH untuk melakukan pertanyaan Redis menggunakan pengumpulan objek, dan Jika anda membandingkan prestasi mencipta sambungan Redis secara normal dan kemudian bertanya untuk menutup sambungan, anda akan mendapati bahawa prestasi kedua-duanya adalah sangat berbeza. Berikut adalah keputusan ujian Ia boleh didapati bahawa prestasi selepas menggunakan pengumpulan objek adalah kira-kira 5 kali ganda daripada kaedah bukan pengumpulan.

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 类的继承关系图也可以发现。

Bagaimana untuk melaksanakan kolam objek di Jawa

Atas ialah kandungan terperinci Bagaimana untuk melaksanakan kolam objek di Jawa. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam