溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中對(duì)象池如何實(shí)現(xiàn)

發(fā)布時(shí)間:2023-05-12 11:07:29 來(lái)源:億速云 閱讀:79 作者:iii 欄目:編程語(yǔ)言

這篇“Java中對(duì)象池如何實(shí)現(xiàn)”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“Java中對(duì)象池如何實(shí)現(xiàn)”文章吧。

1. 什么是對(duì)象池

池化并不是什么新鮮的技術(shù),它更像一種軟件設(shè)計(jì)模式,主要功能是緩存一組已經(jīng)初始化的對(duì)象,以供隨時(shí)可以使用。對(duì)象池大多數(shù)場(chǎng)景下都是緩存著創(chuàng)建成本過(guò)高或者需要重復(fù)創(chuàng)建使用的對(duì)象,從池子中取對(duì)象的時(shí)間是可以預(yù)測(cè)的,但是新建一個(gè)對(duì)象的時(shí)間是不確定的。

當(dāng)需要一個(gè)新對(duì)象時(shí),就向池中借出一個(gè),然后對(duì)象池標(biāo)記當(dāng)前對(duì)象正在使用,使用完畢后歸還到對(duì)象池,以便再次借出。

常見(jiàn)的使用對(duì)象池化場(chǎng)景:

  • 1. 對(duì)象創(chuàng)建成本過(guò)高。

  • 2. 需要頻繁的創(chuàng)建大量重復(fù)對(duì)象,會(huì)產(chǎn)生很多內(nèi)存碎片。

  • 3. 同時(shí)使用的對(duì)象不會(huì)太多。

  • 4. 常見(jiàn)的具體場(chǎng)景如數(shù)據(jù)庫(kù)連接池、線程池等。

2. 為什么需要對(duì)象池

如果一個(gè)對(duì)象的創(chuàng)建成本很高,比如建立數(shù)據(jù)庫(kù)的連接時(shí)耗時(shí)過(guò)長(zhǎng),在不使用池化技術(shù)的情況下,我們的查詢(xún)過(guò)程可能是這樣的。

  • 查詢(xún) 1:建立數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 關(guān)閉連接

  • 查詢(xún) 2:建立數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 關(guān)閉連接

  • 查詢(xún) 3:建立數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 關(guān)閉連接

在這種模式下,每次查詢(xún)都要重新建立關(guān)閉連接,因?yàn)榻⑦B接是一個(gè)耗時(shí)的操作,所以這種模式會(huì)影響程序的總體性能。

那么使用池化思想是怎么樣的呢?同樣的過(guò)程會(huì)轉(zhuǎn)變成下面的步驟。

  • 初始化:建立 N 個(gè)數(shù)據(jù)庫(kù)連接 -> 緩存起來(lái)

  • 查詢(xún) 1:從緩存借到數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 歸還數(shù)據(jù)庫(kù)連接對(duì)象到緩存

  • 查詢(xún) 2:從緩存借到數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 歸還數(shù)據(jù)庫(kù)連接對(duì)象到緩存

  • 查詢(xún) 3:從緩存借到數(shù)據(jù)庫(kù)連接 -> 發(fā)起查詢(xún) -> 收到響應(yīng) -> 歸還數(shù)據(jù)庫(kù)連接對(duì)象到緩存

使用池化思想后,數(shù)據(jù)庫(kù)連接并不會(huì)頻繁的創(chuàng)建關(guān)閉,而是啟動(dòng)后就初始化了 N 個(gè)連接以供后續(xù)使用,使用完畢后歸還對(duì)象,這樣程序的總體性能得到提升。

3. 對(duì)象池的實(shí)現(xiàn)

通過(guò)上面的例子也可以發(fā)現(xiàn)池化思想的幾個(gè)關(guān)鍵步驟:初始化、借出、歸還。上面沒(méi)有展示銷(xiāo)毀步驟, 某些場(chǎng)景下還需要對(duì)象的銷(xiāo)毀這一過(guò)程,比如釋放連接。

下面我們手動(dòng)實(shí)現(xiàn)一個(gè)簡(jiǎn)陋的對(duì)象池,加深下對(duì)對(duì)象池的理解。主要是定一個(gè)對(duì)象池管理類(lèi),然后在里面實(shí)現(xiàn)對(duì)象的初始化、借出、歸還、銷(xiāo)毀等操作。

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;
    // 對(duì)象池棧。后進(jìn)先出
    private Stack<T> stackPool = new Stack<>();
    // 借出的對(duì)象的 hashCode 集合
    private HashSet<Integer> borrowHashCodeSet = new HashSet<>();

    /**
     * 增加一個(gè)對(duì)象
     *
     * @param t
     */
    public synchronized void addObj(T t) {
        if ((stackPool.size() + borrowHashCodeSet.size()) == size) {
            throw new RuntimeException("池中對(duì)象已經(jīng)達(dá)到最大值");
        }
        stackPool.add(t);
        System.out.println("添加了對(duì)象:" + t.hashCode());
    }

    /**
     * 借出一個(gè)對(duì)象
     *
     * @return
     */
    public synchronized T borrowObj() {
        if (stackPool.isEmpty()) {
            System.out.println("沒(méi)有可以被借出的對(duì)象");
            return null;
        }
        T pop = stackPool.pop();
        borrowHashCodeSet.add(pop.hashCode());
        System.out.println("借出了對(duì)象:" + pop.hashCode());
        return pop;
    }

    /**
     * 歸還一個(gè)對(duì)象
     *
     * @param t
     */
    public synchronized void returnObj(T t) {
        if (borrowHashCodeSet.contains(t.hashCode())) {
            stackPool.add(t);
            borrowHashCodeSet.remove(t.hashCode());
            System.out.println("歸還了對(duì)象:" + t.hashCode());
            return;
        }
        throw new RuntimeException("只能歸還從池中借出的對(duì)象");
    }

    /**
     * 銷(xiāo)毀池中對(duì)象
     */
    public synchronized void destory() {
        if (!borrowHashCodeSet.isEmpty()) {
            throw new RuntimeException("尚有未歸還的對(duì)象,不能關(guān)閉所有對(duì)象");
        }
        while (!stackPool.isEmpty()) {
            T pop = stackPool.pop();
            try {
                pop.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("已經(jīng)銷(xiāo)毀了所有對(duì)象");
    }
}

代碼還是比較簡(jiǎn)單的,只是簡(jiǎn)單的示例,下面我們通過(guò)池化一個(gè) Redis 連接對(duì)象 Jedis 來(lái)演示如何使用。

其實(shí) Jedis 中已經(jīng)有對(duì)應(yīng)的 Jedis 池化管理對(duì)象了 JedisPool 了,不過(guò)我們這里為了演示對(duì)象池的實(shí)現(xiàn),就不使用官方提供的 JedisPool 了。

啟動(dòng)一個(gè) Redis 服務(wù)這里不做介紹,假設(shè)你已經(jīng)有了一個(gè) Redis 服務(wù),下面引入 Java 中連接 Redis 需要用到的 Maven 依賴(lài)。

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

正常情況下 Jedis 對(duì)象的使用方式:

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

如果使用上面的對(duì)象池,就可以像下面這樣使用。

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<>();
        // 增加一個(gè) jedis 連接對(duì)象
        objectPool.addObj(new Jedis("127.0.0.1", 6379));
        objectPool.addObj(new Jedis("127.0.0.1", 6379));
        // 從對(duì)象池中借出一個(gè) jedis 對(duì)象
        Jedis jedis = objectPool.borrowObj();
        // 一次 redis 查詢(xún)
        String name = jedis.get("name");
        System.out.println(String.format("redis get:" + name));
        // 歸還 redis 連接對(duì)象
        objectPool.returnObj(jedis);
        // 銷(xiāo)毀對(duì)象池中的所有對(duì)象
        objectPool.destory();
        // 再次借用對(duì)象
        objectPool.borrowObj();
    }
}

輸出日志:

添加了對(duì)象:1556956098
添加了對(duì)象:1252585652
借出了對(duì)象:1252585652
redis get:www.wdbyte.com
歸還了對(duì)象:1252585652
已經(jīng)銷(xiāo)毀了所有對(duì)象
沒(méi)有可以被借出的對(duì)象

如果使用 JMH 對(duì)使用對(duì)象池化進(jìn)行 Redis 查詢(xún),和正常創(chuàng)建 Redis 連接然后查詢(xún)關(guān)閉連接的方式進(jìn)行性能對(duì)比,會(huì)發(fā)現(xiàn)兩者的性能差異很大。下面是測(cè)試結(jié)果,可以發(fā)現(xiàn)使用對(duì)象池化后的性能是非池化方式的 5 倍左右。

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

4. 開(kāi)源的對(duì)象池工具

上面自己實(shí)現(xiàn)的對(duì)象池總歸有些簡(jiǎn)陋了,其實(shí)開(kāi)源工具中已經(jīng)有了非常好用的對(duì)象池的實(shí)現(xiàn),如 Apache 的 commons-pool2 工具,很多開(kāi)源工具中的對(duì)象池都是基于此工具實(shí)現(xiàn),下面介紹這個(gè)工具的使用方式。

maven 依賴(lài):

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

commons-pool2 對(duì)象池工具中有幾個(gè)關(guān)鍵的類(lèi)。

  • &bull; PooledObjectFactory 類(lèi)是一個(gè)工廠接口,用于實(shí)現(xiàn)想要池化對(duì)象的創(chuàng)建、驗(yàn)證、銷(xiāo)毀等操作。

  • &bull; GenericObjectPool 類(lèi)是一個(gè)通用的對(duì)象池管理類(lèi),可以進(jìn)行對(duì)象的借出、歸還等操作。

  • &bull; GenericObjectPoolConfig 類(lèi)是對(duì)象池的配置類(lèi),可以進(jìn)行對(duì)象的最大、最小等容量信息進(jìn)行配置。

下面通過(guò)一個(gè)具體的示例演示 commons-pool2 工具類(lèi)的使用,這里依舊選擇 Redis 連接對(duì)象 Jedis 作為演示。

實(shí)現(xiàn) PooledObjectFactory 工廠類(lèi),實(shí)現(xiàn)其中的對(duì)象創(chuàng)建和銷(xiāo)毀方法。

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 類(lèi),實(shí)現(xiàn)對(duì)對(duì)象的借出、歸還等操作。

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 類(lèi)的構(gòu)造函數(shù)中的入?yún)⒂?GenericObjectPoolConfig 對(duì)象,這是個(gè)對(duì)象池的配置對(duì)象,可以配置對(duì)象池的容量大小等信息,這里就不配置了,使用默認(rèn)配置。

通過(guò) GenericObjectPoolConfig 的源碼可以看到默認(rèn)配置中,對(duì)象池的容量是 8 個(gè)。

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;

下面編寫(xiě)一個(gè)對(duì)象池使用測(cè)試類(lèi)。

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
釋放連接

上面已經(jīng)演示了 commons-pool2 工具中的對(duì)象池的使用方式,從上面的例子中可以發(fā)現(xiàn)這種對(duì)象池中只能存放同一種初始化條件的對(duì)象,如果這里的 Redis 我們需要存儲(chǔ)一個(gè)本地連接和一個(gè)遠(yuǎn)程連接的兩種 Jedis 對(duì)象,就不能滿足了。那么怎么辦呢?

其實(shí) commons-pool2 工具已經(jīng)考慮到了這種情況,通過(guò)增加一個(gè) key 值可以在同一個(gè)對(duì)象池管理中進(jìn)行區(qū)分,代碼和上面類(lèi)似,直接貼出完整的代碼實(shí)現(xiàn)。

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 對(duì)象池實(shí)現(xiàn)分析

這篇文章中的演示都使用了 Jedis 連接對(duì)象,其實(shí)在 Jedis SDK 中已經(jīng)實(shí)現(xiàn)了相應(yīng)的對(duì)象池,也就是我們常用的 JedisPool 類(lèi)。那么這里的 JedisPool 是怎么實(shí)現(xiàn)的呢?我們先看一下 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);
        // 從對(duì)象池中借一個(gè)對(duì)象
        Jedis jedis = jedisPool.getResource();
        String name = jedis.get("name");
        System.out.println("redis get :" + name);
        jedis.close();
        // 徹底退出前,關(guān)閉 Redis 連接池
        jedisPool.close();
    }
}

代碼中添加了注釋?zhuān)梢钥吹酵ㄟ^(guò) jedisPool.getResource() 拿到了一個(gè)對(duì)象,這里和上面 commons-pool2 工具中的 borrowObject 十分相似,繼續(xù)追蹤它的代碼實(shí)現(xiàn)可以看到下面的代碼。

// redis.clients.jedis.JedisPool
// public class JedisPool extends Pool<Jedis> {
public Jedis getResource() {
    Jedis jedis = (Jedis)super.getResource();
    jedis.setDataSource(this);
    return jedis;
}
// 繼續(xù)追蹤 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() ,多么熟悉的方法,繼續(xù)分析代碼可以發(fā)現(xiàn) Jedis 對(duì)象池也是使用了 commons-pool2 工具作為實(shí)現(xiàn)。既然如此,那么 jedis.close() 方法的邏輯我們應(yīng)該也可以猜到了,應(yīng)該有一個(gè)歸還的操作,查看代碼發(fā)現(xiàn)果然如此。

// 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();
    }
}
// 繼續(xù)追蹤 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);
        }
    }
}

通過(guò)上面的分析,可見(jiàn) Jedis 確實(shí)使用了 commons-pool2 工具進(jìn)行對(duì)象池的管理,通過(guò)分析 JedisPool 類(lèi)的繼承關(guān)系圖也可以發(fā)現(xiàn)。

Java中對(duì)象池如何實(shí)現(xiàn)

以上就是關(guān)于“Java中對(duì)象池如何實(shí)現(xiàn)”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI