關於對象池的概念這裏不做過多解釋,除了用來緩存數據庫連接java.sql.Connection這種重量級對象也能用於其他場景。初始化對象的代價高,就值得池化。池化的一個代價是被重用的對象會在堆中停留很長時間。如果有大量對象存在於堆中,那用來創建新對象的空間就少了,因爲GC 操作會更爲頻繁。
以SimpleDateFormat 爲例,這是 Java 中常用的一個類,用於解析和格式化日期字符串。
但是 SimpleDateFormat 在多線程環境中並不是線程安全的。詳見這位仁兄的 SimpleDateFormat 的線程安全問題與 ThreadLocal
實現線程安全的一種思路是用ThreadLocal進行安全變量的副本。
static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public String formatDate(Date date) {
return format1.get().format(date);
}
但是維持變量副本的資源也是需要消耗資源的。儘管ThreadLocal提供了remove方法以便在請求結束後釋放副本,但若在請求結束的時候崩潰副本沒有得到釋放,將會給內存造成極大的壓力。爲了應對這種情況我們可以採用jdbc連接池的思想,把對象放入對象池工具裏面,用"pool"來約束對象的創建與銷燬。
首先引入Apache commons-pool依賴
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
接口的聲明
//接口
import java.util.Date;
public interface FormattingService {
String format(Date date);
void setPattern(String pattern);
}
實現類
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;
public class PooledFormattingService implements FormattingService {
// 對象池的實現
GenericObjectPool<SimpleDateFormat> dateFormatsPool;
private static AtomicInteger count = new AtomicInteger(0);
String pattern;
public PooledFormattingService() {
// 對象池工廠
PoolableObjectFactory factory = new BasePoolableObjectFactory() {
@Override
public Object makeObject() throws Exception {
System.out.println("創建新對象"+(count.incrementAndGet()));
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
dateFormatsPool = new GenericObjectPool(factory);
// 這裏設置創建新對象的最大數目
dateFormatsPool.setMaxActive(10);
}
public String format(Date date) {
try {
SimpleDateFormat dateFormat = this.dateFormatsPool.borrowObject();
try {
return dateFormat.format(date);
} finally {
this.dateFormatsPool.returnObject(dateFormat);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setPattern(final String pattern) {
PoolableObjectFactory factory = new BasePoolableObjectFactory() {
@Override
public Object makeObject() throws Exception {
return new SimpleDateFormat(pattern);
}
};
this.dateFormatsPool = new GenericObjectPool(factory);
}
}
測試
import java.util.Date;
public class Mytest {
public static void main(String[] args) {
PooledFormattingService pooledFormattingService = new PooledFormattingService();
//創建20個線程去調用SimpleDateFormat
//由於對象池爲10,SimpleDateFormat最多創建10個
for (int i = 0; i < 20; i++) {
new Thread(() -> {
pooledFormattingService.format(new Date());
}).start();
}
}
}
核心實現都在 org.apache.commons.pool.impl.GenericObjectPool裏面
這裏只展示幾個關鍵參數
maxActive: 鏈接池中最大連接數,默認爲8.
maxIdle: 鏈接池中最大空閒的連接數,默認爲8.
minIdle: 連接池中最少空閒的連接數,默認爲0.
maxWait: 當連接池資源耗盡時,調用者最大阻塞的時間,超時將拋異常。默認永不超時.
minEvictableIdleTimeMillis: 連接空閒的最小時間,達到此值後空閒連接將可能會被移除。
softMinEvictableIdleTimeMillis: 連接空閒的最小時間,達到此值後空閒鏈接將會被移除
numTestsPerEvictionRun: 對於“空閒鏈接”檢測線程而言,每次檢測的鏈接資源的個數。默認爲3.