前言
在Java應(yīng)用程序中,頻繁地創(chuàng)建和銷毀對象會消耗大量的內(nèi)存和CPU資源,影響應(yīng)用程序的性能和可伸縮性。為了解決這個問題,我們可以使用對象池技術(shù),將對象存儲在池中,在需要的時候從池中獲取,使用完畢后將對象歸還到池中。Apache Commons Pool2是一個流行的開源對象池實(shí)現(xiàn),提供了豐富的功能和配置選項,可以滿足不同應(yīng)用程序的需求。
Commons Pool2
Commons Pool2是一個流行的開源對象池實(shí)現(xiàn),提供了豐富的接口和配置選項,使得實(shí)現(xiàn)對象池變得非常容易,并且可以根據(jù)具體的業(yè)務(wù)需求進(jìn)行靈活的配置。Commons Pool2還支持多線程共享對象池中的對象,避免線程之間的競爭,從而提高應(yīng)用程序的并發(fā)性能。
實(shí)現(xiàn)一個簡單的對象池
下面讓我們通過理解和使用Commons Pool2的BasePooledObjectFactory、GenericObjectPool和GenericObjectPoolConfig三個核心接口和類來實(shí)現(xiàn)一個對象池。
引入依賴
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.0</version>
</dependency>
BasePooledObjectFactory
首先,讓我們看一下BasePooledObjectFactory類。這個類是一個抽象類,可以用于創(chuàng)建和銷毀池中的對象。要使用這個類,我們需要繼承它并重寫以下方法:
create():用于創(chuàng)建對象,返回一個新的對象。
wrap(T obj):用于包裝對象,返回一個PooledObject對象,其中包含了對象本身以及對象狀態(tài)信息。
validateObject(PooledObject p):用于驗(yàn)證對象是否可用,返回一個布爾值,表示對象是否可用。
destroyObject(PooledObject p):用于銷毀對象。
讓我們先定義一個對象類,可以在里面編寫一些對象的創(chuàng)建、銷毀和是否可用等方法,用于示例展示或者測試用途的。
public class MyObject {
private String name;
public MyObject(String name) {
this.name = name;
}
public void create() {
System.out.println("ThreadName:"+ Thread.currentThread().getName() + " 對象:" + name + "正在被創(chuàng)建。。。。。。");
}
public void destroy() {
System.out.println("ThreadName:"+ Thread.currentThread().getName() + " 對象:" + name + "正在被銷毀。。。。。。");
}
public boolean isValid() {
System.out.println("ThreadName:"+ Thread.currentThread().getName() + " 對象" + name + "正在檢驗(yàn)是否可用。。。。。。");
return true;
}
}
public class MyObjectFactory extends BasePooledObjectFactory<MyObject> {
@Override
public MyObject create() throws Exception {
// 創(chuàng)建一個新的MyObject對象
MyObject myObject = new MyObject(UUID.randomUUID().toString());
myObject.create();
return myObject;
}
@Override
public PooledObject<MyObject> wrap(MyObject myObject) {
// 將MyObject對象封裝到一個PooledObject對象中并返回
return new DefaultPooledObject<>(myObject);
}
@Override
public void destroyObject(PooledObject<MyObject> pooledObject) throws Exception {
// 銷毀對象
MyObject myObject = pooledObject.getObject();
myObject.destroy();
}
@Override
public boolean validateObject(PooledObject<MyObject> pooledObject) {
// 驗(yàn)證對象是否可用
MyObject myObject = pooledObject.getObject();
return myObject.isValid();
}
}
GenericObjectPoolConfig
接下來,讓我們看一下GenericObjectPoolConfig類。這個類是一個配置類,用于配置對象池的屬性,例如池大小、最大等待時間、是否允許對象為null等。使用這個類,我們可以根據(jù)應(yīng)用程序的需求來自定義對象池的行為和配置。
下面列出一些配置的默認(rèn)值和推薦值以及說明。
GenericObjectPoolConfig<MyObject> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(8); // 對象池中最大對象數(shù)
config.setMaxIdle(4); // 對象池中最大空閑對象數(shù)
config.setMinIdle(2); // 對象池中最小空閑對象數(shù)
config.setBlockWhenExhausted(true); // 當(dāng)對象池耗盡時,是否等待獲取對象
config.setMaxWaitMillis(-1L); // 對象池沒有對象可用時,最大等待時間(單位:毫秒),-1表示無限等待
config.setTestOnCreate(false); // 創(chuàng)建對象時是否進(jìn)行對象有效性檢查
config.setTestOnBorrow(false); // 借出對象時是否進(jìn)行對象有效性檢查
config.setTestOnReturn(false); // 歸還對象時是否進(jìn)行對象有效性檢查
config.setTestWhileIdle(false); // 空閑時是否進(jìn)行對象有效性檢查
config.setMinEvictableIdleTimeMillis(1800000L); // 對象池中對象的最小空閑時間(單位:毫秒)
config.setTimeBetweenEvictionRunsMillis(-1L); // 后臺對象回收器運(yùn)行的時間間隔(單位:毫秒),-1表示不運(yùn)行
config.setNumTestsPerEvictionRun(3); // 后臺對象回收器運(yùn)行時檢查對象的個數(shù)
config.setSoftMinEvictableIdleTimeMillis(-1L); // 對象池中對象的最小空閑時間,當(dāng)空閑對象的數(shù)目大于最小空閑數(shù)(minIdle)且空閑時間超過此值時,對象將被移除
config.setLifo(true); // 是否使用后進(jìn)先出原則借出對象
config.setFairness(false); // 是否使用公平鎖
config.setJmxEnabled(true); // 是否開啟 JMX 監(jiān)控
GenericObjectPool
最后,讓我們看一下GenericObjectPool類。這個類是對象池的主要實(shí)現(xiàn),可以用于從池中獲取對象、將對象返回到池中以及獲取當(dāng)前池中的對象數(shù)量等。在創(chuàng)建對象池時,我們需要提供一個對象工廠類和一個對象池配置類。
使用GenericObjectPool類,我們可以使用以下方法:
borrowObject():用于從對象池中獲取對象。如果對象池中沒有可用對象,則此方法將阻塞,直到有可用對象為止。
returnObject(T obj):用于將對象返回到對象池中。
getNumIdle():用于獲取當(dāng)前空閑對象的數(shù)量。
getNumActive():用于獲取當(dāng)前正在使用的對象的數(shù)量。
close():用于關(guān)閉對象池。
我這里使用枚舉來實(shí)現(xiàn)對象池的創(chuàng)建,因?yàn)槊杜e類型是一個線程安全的單例模式,可以避免多線程并發(fā)訪問時可能出現(xiàn)的競態(tài)條件和同步問題。確保在整個應(yīng)用程序中只有一個對象池實(shí)例存在,可以有效地避免多個對象池實(shí)例之間可能出現(xiàn)的沖突和資源浪費(fèi)問題。
public enum MyObjectPool {
/**
* 線程安全的單例
*/
INSTANCE;
private GenericObjectPool<MyObject> objectPool;
MyObjectPool() {
// 創(chuàng)建對象池配置
GenericObjectPoolConfig<MyObject> poolConfig = new GenericObjectPoolConfig<>();
// 對象池中最大對象數(shù)
poolConfig.setMaxTotal(8);
// 對象池中最小空閑對象數(shù)
poolConfig.setMinIdle(2);
// 對象池中最大空閑對象數(shù)
poolConfig.setMaxIdle(4);
// 當(dāng)對象池耗盡時,是否等待獲取對象
poolConfig.setBlockWhenExhausted(true);
// 創(chuàng)建對象時是否進(jìn)行對象有效性檢查
poolConfig.setTestOnCreate(true);
// 借出對象時是否進(jìn)行對象有效性檢查
poolConfig.setTestOnBorrow(true);
// 歸還對象時是否進(jìn)行對象有效性檢查
poolConfig.setTestOnReturn(true);
// 空閑時是否進(jìn)行對象有效性檢查
poolConfig.setTestWhileIdle(true);
// 創(chuàng)建對象工廠
MyObjectFactory objectFactory = new MyObjectFactory();
// 創(chuàng)建對象池
objectPool = new GenericObjectPool<>(objectFactory, poolConfig);
}
public MyObject borrowObject() throws Exception {
// 從對象池中借出一個對象
return objectPool.borrowObject();
}
public void returnObject(MyObject myObject) {
// 將對象歸還給對象池
objectPool.returnObject(myObject);
}
public int getNumActive() throws Exception {
// 從對象池中借出一個對象
return objectPool.getNumActive();
}
public int getNumIdle() throws Exception {
// 從對象池中借出一個對象
return objectPool.getNumIdle();
}
}
測試
下面我們編寫一個簡單的測試方法,模擬單線程和多線程兩種情況下,對象池管理對象的借出和歸還的情況
public class PoolTest {
public static void main(String[] args) throws Exception {
// singleTest();
threadTest();
}
public static void singleTest() throws Exception{
MyObjectPool myObjectPool = MyObjectPool.INSTANCE;
numActiveAndNumIdle(myObjectPool);
Thread.sleep(1000);
MyObject obj = myObjectPool.borrowObject();
System.out.println("ThreadName:"+ Thread.currentThread().getName() + " borrowed: " + JSONObject.toJSONString(obj));
Thread.sleep(1000);
numActiveAndNumIdle(myObjectPool);
Thread.sleep(1000);
myObjectPool.returnObject(obj);
System.out.println("ThreadName:"+ Thread.currentThread().getName() + " returned: " + JSONObject.toJSONString(obj));
Thread.sleep(1000);
numActiveAndNumIdle(myObjectPool);
}
private static void numActiveAndNumIdle(MyObjectPool myObjectPool) throws Exception{
int numActive = myObjectPool.getNumActive();
int numIdle = myObjectPool.getNumIdle();
System.out.println("ThreadName:"+ Thread.currentThread().getName() + " numActive:" + numActive + " numIdle:" + numIdle);
}
public static void threadTest() throws Exception{
ExecutorService executorService = Executors.newFixedThreadPool(9);
for (int i = 0; i < 20; i++) {
executorService.submit(() -> {
try {
singleTest();
} catch (Exception e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
}
總結(jié)
相比于其他實(shí)現(xiàn)對象池的技術(shù),使用 Commons Pool2 實(shí)現(xiàn)對象池的優(yōu)點(diǎn)是:它提供了完整的對象池管理功能,包括對象的創(chuàng)建、初始化、借用、歸還、清理和銷毀等操作,并且支持多線程環(huán)境下的并發(fā)訪問和線程安全。此外,Commons Pool2 還具有靈活的配置選項,可以根據(jù)具體場景對對象池的性能和資源消耗進(jìn)行優(yōu)化。文章來源:http://www.zghlxwxcb.cn/news/detail-405281.html
缺點(diǎn)是,使用 Commons Pool2 實(shí)現(xiàn)對象池需要引入額外的依賴,增加了項目的復(fù)雜性。此外,實(shí)現(xiàn)和配置對象池需要一定的技術(shù)能力,需要了解對象池的原理和相關(guān)的配置參數(shù),否則可能會導(dǎo)致對象池的性能和穩(wěn)定性問題。需要注意的問題包括對象池的配置參數(shù),對象池的線程安全性,對象的有效性檢查和對象的回收策略等。文章來源地址http://www.zghlxwxcb.cn/news/detail-405281.html
到了這里,關(guān)于使用Apache Commons Pool2創(chuàng)建Java對象池的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!