目錄
?引言
1、問題描述
2、SpringBoot默認(rèn)的數(shù)據(jù)庫連接池
3、HikariCP是什么
4、測試依賴
5、配置文件
5.1、數(shù)據(jù)庫連接參數(shù)
5.2、連接池數(shù)據(jù)基本參數(shù)
5.3、連接檢查參數(shù)
5.4、事務(wù)相關(guān)參數(shù)
5.5、JMX參數(shù)
6、HikariCP源碼淺析
6.1、HikariConfig--連接池配置的加載
6.2、HikariPool--連接池
1、HikariPool UML圖
2、PoolBase
3、HikariPool
4、如何獲取一個鏈接對象
6.3、ConcurrentBag--更少的鎖沖突
7、HikariCP為什么快?
7.1、通過代碼設(shè)計和優(yōu)化大幅減少線程間的鎖競爭
7.2、引入了更多 JDK 的特性
7.3、使用 javassist 直接修改 class 文件生成動態(tài)代理
8、JDK 、CGLib 、ASM 、Javassist 性能測試
1、測試代碼
2、測試結(jié)果
?引言
????????咱們開發(fā)項目的過程中用到很多的開源數(shù)據(jù)庫鏈接池,比如druid、c3p0、BoneCP等等,前端時間在部署新服務(wù)的時候發(fā)現(xiàn)了個問題,排查完畢問題正好學(xué)習(xí)學(xué)習(xí)SpringBoot的默認(rèn)的數(shù)據(jù)庫連接池HikariCP的一些知識。HikariCP官網(wǎng)地址:?https://github.com/brettwooldridge/HikariCP
1、問題描述
? ? ? ? 我們新項目部署上線之后在觀察日志的時候發(fā)現(xiàn)了這個警告,經(jīng)過排查是發(fā)現(xiàn)DB方面的問題,保留現(xiàn)場如下。
2、SpringBoot默認(rèn)的數(shù)據(jù)庫連接池
????????Spring-Boot-2.0.0-M1版本將默認(rèn)的數(shù)據(jù)庫連接池從tomcat jdbc pool改為了HikariCP。
3、HikariCP是什么
????????HikariCP 是用于創(chuàng)建和管理連接,利用“池”的方式復(fù)用連接減少資源開銷,和其他數(shù)據(jù)源一樣,也具有連接數(shù)控制、連接可靠性測試、連接泄露控制、緩存語句等功能,另外,和 druid 一樣,HikariCP 也支持監(jiān)控功能。
????????HikariCP 是目前最快的連接池,就連風(fēng)靡一時的 BoneCP 也停止維護,主動讓位給它,SpringBoot 也把它設(shè)置為默認(rèn)連接池。
4、測試依賴
? ? ? ? 既然官網(wǎng)說HikariCP是最快的數(shù)據(jù)庫連接池,不妨我們進行一些嘗試,驗證一下官網(wǎng)放出的狠話。驗證也比較簡單,只需要在項目中添加依賴即可。
<!-- JNDI數(shù)據(jù)源 -->
<resource-ref>
<res-ref-name>jdbc/hikariCP-test</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
5、配置文件
? ? ? ? 上面一步添加完依賴,接下來具體實操之前先了解一下HikariCP的各種配置信息。
5.1、數(shù)據(jù)庫連接參數(shù)
????????注意,這里url在后面拼接了多個參數(shù)用于避免亂碼、時區(qū)報錯問題。
#-------------基本屬性--------------------------------
jdbcUrl=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true
username=root
password=root
#JDBC驅(qū)動使用的Driver實現(xiàn)類類名
#默認(rèn)為空。會根據(jù)jdbcUrl來解析
driverClassName=com.mysql.cj.jdbc.Driver
5.2、連接池數(shù)據(jù)基本參數(shù)
#-------------連接池大小相關(guān)參數(shù)--------------------------------
#最大連接池數(shù)量
#默認(rèn)為10??赏ㄟ^JMX動態(tài)修改
maximumPoolSize=10
#最小空閑連接數(shù)量
#默認(rèn)與maximumPoolSize一致??赏ㄟ^JMX動態(tài)修改
minimumIdle=0
5.3、連接檢查參數(shù)
????????注意:針對連接失效的問題,HikariCP 強制開啟借出測試和空閑測試,不開啟回收測試,可選的只有泄露測試。所有的超時時間都可以根據(jù)JMX設(shè)置。
#-------------連接檢測情況--------------------------------
#用來檢測連接是否有效的sql,要求是一個查詢語句,常用select 'x'
#如果驅(qū)動支持JDBC4,建議不設(shè)置,因為這時默認(rèn)會調(diào)用Connection.isValid()方法來檢測,該方式效率會更高
#默認(rèn)為空
connectionTestQuery=select 1 from dual
#檢測連接是否有效的超時時間,單位毫秒
#最小允許值250 ms
#默認(rèn)5000 ms。
validationTimeout=5000
#連接保持空閑而不被驅(qū)逐的最小時間。單位毫秒。
#該配置只有再minimumIdle < maximumPoolSize才會生效,最小允許值為10000 ms。
#默認(rèn)值10000*60 = 10分鐘。
idleTimeout=600000
#連接對象允許“泄露”的最大時間。單位毫秒
#最小允許值為2000 ms。
#默認(rèn)0,表示不開啟泄露檢測。
leakDetectionThreshold=0
#連接最大存活時間。單位毫秒
#最小允許值30000 ms
#默認(rèn)30分鐘??赏ㄟ^JMX動態(tài)修改
maxLifetime=1800000
#獲取連接時最大等待時間,單位毫秒
#獲取時間超過該配置,將拋出異常。最小允許值250 ms
#默認(rèn)30000 ms。
connectionTimeout=300000
5.4、事務(wù)相關(guān)參數(shù)
#-------------事務(wù)相關(guān)的屬性--------------------------------
#當(dāng)連接返回池中時是否設(shè)置自動提交
#默認(rèn)為true
autoCommit=true
#當(dāng)連接從池中取出時是否設(shè)置為只讀
#默認(rèn)值false
readOnly=false
#連接池創(chuàng)建的連接的默認(rèn)的TransactionIsolation狀態(tài)
#可用值為下列之一:NONE,TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE
#默認(rèn)值為空,由驅(qū)動決定
transactionIsolation=TRANSACTION_REPEATABLE_READ
5.5、JMX參數(shù)
#-------------JMX--------------------------------
#是否允許通過JMX掛起和恢復(fù)連接池
#默認(rèn)為false
allowPoolSuspension=false
#是否開啟JMX
#默認(rèn)false
registerMbeans=true
#數(shù)據(jù)源名。
#默認(rèn)自動生成
poolName=
6、HikariCP源碼淺析
6.1、HikariConfig--連接池配置的加載
????????在HikariCP 中,HikariConfig用于加載配置,它的加載要更加簡潔。直接從PropertyElf.setTargetFromProperties(Object, Properties)方法開始看。
// 這個方法就是將properties的參數(shù)設(shè)置到HikariConfig中
public static void setTargetFromProperties(final Object target, final Properties properties)
{
if (target == null || properties == null) {
return;
}
// 在這里會利用反射獲取
List<Method> methods = Arrays.asList(target.getClass().getMethods());
// 遍歷
properties.forEach((key, value) -> {
if (target instanceof HikariConfig && key.toString().startsWith("dataSource.")) {
// 如果是dataSource.*的參數(shù),直接加入到dataSourceProperties屬性
((HikariConfig) target).addDataSourceProperty(key.toString().substring("dataSource.".length()), value);
}
else {
// 如果不是,則通過set方法設(shè)置
setProperty(target, key.toString(), value, methods);
}
});
}
private static void setProperty(final Object target, final String propName, final Object propValue, final List<Method> methods)
{
final Logger logger = LoggerFactory.getLogger(PropertyElf.class);
// use the english locale to avoid the infamous turkish locale bug
// 拼接參數(shù)的setter方法名 首字母大寫
String methodName = "set" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);
// 獲取對應(yīng)的Method 對象
Method writeMethod = methods.stream().filter(m -> m.getName().equals(methodName) && m.getParameterCount() == 1).findFirst().orElse(null);
// 如果不存在,按另一套規(guī)則拼接參數(shù)的setter方法名 全部大寫
if (writeMethod == null) {
String methodName2 = "set" + propName.toUpperCase(Locale.ENGLISH);
writeMethod = methods.stream().filter(m -> m.getName().equals(methodName2) && m.getParameterCount() == 1).findFirst().orElse(null);
}
// 如果該參數(shù)setter方法不存在,則拋出異常,從這里可以看出,HikariCP 中不能存在配錯參數(shù)名的情況
if (writeMethod == null) {
logger.error("Property {} does not exist on target {}", propName, target.getClass());
throw new RuntimeException(String.format("Property %s does not exist on target %s", propName, target.getClass()));
}
// 調(diào)用setter方法來配置具體參數(shù)。
try {
Class<?> paramClass = writeMethod.getParameterTypes()[0];
if (paramClass == int.class) {
writeMethod.invoke(target, Integer.parseInt(propValue.toString()));
}
else if (paramClass == long.class) {
writeMethod.invoke(target, Long.parseLong(propValue.toString()));
}
else if (paramClass == boolean.class || paramClass == Boolean.class) {
writeMethod.invoke(target, Boolean.parseBoolean(propValue.toString()));
}
else if (paramClass == String.class) {
writeMethod.invoke(target, propValue.toString());
}
else {
try {
logger.debug("Try to create a new instance of \"{}\"", propValue.toString());
writeMethod.invoke(target, Class.forName(propValue.toString()).newInstance());
}
catch (InstantiationException | ClassNotFoundException e) {
logger.debug("Class \"{}\" not found or could not instantiate it (Default constructor)", propValue.toString());
writeMethod.invoke(target, propValue);
}
}
}
catch (Exception e) {
logger.error("Failed to set property {} on target {}", propName, target.getClass(), e);
throw new RuntimeException(e);
}
}
6.2、HikariPool--連接池
????????HikariPool 是一個非常重要的類,它負(fù)責(zé)管理連接。
1、HikariPool UML圖
HikariPoolMXBean:采用JMX控制HikariPool的入口。
/**
* The javax.management MBean for a Hikari pool instance.
*
* @author Brett Wooldridge
*/
public interface HikariPoolMXBean
2、PoolBase
????????HikariPool鏈接池的配置信息。
3、HikariPool
????????連接池的管理。
屬性:
//配置信息。
public final HikariConfig config;
//指標(biāo)記錄器包裝類。HikariCP支持Metrics監(jiān)控
IMetricsTrackerDelegate metricsTracker;
//創(chuàng)建新連接的任務(wù),Callable實現(xiàn)類。一般調(diào)用一次創(chuàng)建一個連接
private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(null /*logging prefix*/);
//創(chuàng)建新連接的任務(wù),Callable實現(xiàn)類。一般調(diào)用一次創(chuàng)建一個連接,與前者區(qū)別在于它創(chuàng)建最后一個連接,會打印日志
private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding ");
private final Collection<Runnable> addConnectionQueueReadOnlyView;
//執(zhí)行PoolEntryCreator任務(wù)的線程池。以addConnectionQueueReadOnlyView作為等待隊列
private final ThreadPoolExecutor addConnectionExecutor;
//執(zhí)行關(guān)閉連接的線程池
private final ThreadPoolExecutor closeConnectionExecutor;
//用于執(zhí)行HouseKeeper(連接檢測任務(wù)和維持連接池大?。┑热蝿?wù)
private final ScheduledExecutorService houseKeepingExecutorService;
//存放連接對象的包。用于borrow、requite、add和remove對象。
private final ConcurrentBag<PoolEntry> connectionBag;
4、如何獲取一個鏈接對象
/**
* Get a connection from the pool, or timeout after the specified number of milliseconds.
*
* @param hardTimeout the maximum time to wait for a connection from the pool
* @return a java.sql.Connection instance
* @throws SQLException thrown if a timeout occurs trying to obtain a connection
*/
public Connection getConnection(final long hardTimeout) throws SQLException
{
// 如果我們設(shè)置了allowPoolSuspension為true,則這個鎖會生效,這個是基于信號量的鎖 MAX_PERMITS = 10000,正常情況不會用完,除非你掛起了連接池(通過JMX等方式),10000個permits會被消耗完
suspendResumeLock.acquire();
final long startTime = currentTime();
try {
// 剩余超時時間
long timeout = hardTimeout;
// 循環(huán)獲取,除非獲取到了連接或者超時
do {
// 從ConcurrentBag中拿出一個元素
PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
// 前面說過,只有超時情況才會返回空,這時會跳出循環(huán)并拋出異常
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
final long now = currentTime();
// 如果
// 1、元素被標(biāo)記為丟棄
// 2、空閑時間過長
// 3、連接無效則會丟棄該元素
// 1&2&3 --> 4、并關(guān)閉連接
if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {
closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
timeout = hardTimeout - elapsedMillis(startTime);
}
else {
metricsTracker.recordBorrowStats(poolEntry, startTime);
// 創(chuàng)建Connection代理類,該代理類就是使用Javassist生成的
return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
}
} while (timeout > 0L);
metricsTracker.recordBorrowTimeoutStats(startTime);
// 超時拋出異常
throw createTimeoutException(startTime);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
}
finally {
// 釋放一個permit
suspendResumeLock.release();
}
}
6.3、ConcurrentBag--更少的鎖沖突
????????在 HikariCP 中ConcurrentBag用于存放PoolEntry對象(封裝了Connection對象,IConcurrentBagEntry實現(xiàn)類),本質(zhì)上可以將它就是一個資源池。
?屬性:
//存放著當(dāng)前線程返還的PoolEntry對象。如果當(dāng)前線程再次借用資源,會先從這個列表中獲取。注意,這個列表的元素可以被其他線程“偷走”
private final ThreadLocal<List<Object>> threadList;
//添加元素的監(jiān)聽器,由HikariPool實現(xiàn),在該實現(xiàn)中,如果waiting - addConnectionQueue.size() >= 0,則會讓addConnectionExecutor執(zhí)行PoolEntryCreator任務(wù)
private final IBagStateListener listener;
//當(dāng)前等待獲取鏈接的線程數(shù)
private final AtomicInteger waiters;
//元素是否使用弱引用
private final boolean weakThreadLocals;
//這是一個無容量的阻塞隊列,每個插入操作需要阻塞等待刪除操作,而刪除操作不需要等待,如果沒有元素插入,會返回null,如果設(shè)置了超時時間則需要等待。
private final SynchronousQueue<T> handoffQueue;
//存放著狀態(tài)為使用中、未使用和保留三種狀態(tài)的PoolEntry對象。注意,CopyOnWriteArrayList是一個線程安全的集合,在每次寫操作時都會采用復(fù)制數(shù)組的方式來增刪元素,讀和寫使用的是不同的數(shù)組,避免了鎖競爭
private final CopyOnWriteArrayList<T> sharedList;
方法:
????????在以下方法中,唯一可能出現(xiàn)線程切換到就是handoffQueue.poll(timeout, NANOSECONDS)。
/**
* The method will borrow a BagEntry from the bag, blocking for the
* specified timeout if none are available.
*
* @param timeout how long to wait before giving up, in units of unit
* @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter
* @return a borrowed instance from the bag or null if a timeout occurs
* @throws InterruptedException if interrupted while waiting
*/
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
{
// 1. 首先從threadList獲取對象
// Try the thread-local list first
// 獲取綁定在當(dāng)前線程的List<Object>對象,注意這個集合的實現(xiàn)一般為FastList,這是HikariCP自己實現(xiàn)的
final List<Object> list = threadList.get();
for (int i = list.size() - 1; i >= 0; i--) {
// 獲取當(dāng)前元素,并將它從集合中刪除
final Object entry = list.remove(i);
@SuppressWarnings("unchecked")
// 如果設(shè)置了weakThreadLocals,則存放的是WeakReference對象
final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
// 采用CAS方式將獲取的對象狀態(tài)由未使用改為使用中,如果失敗說明其他線程正在使用它。
if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry;
}
}
// 2.如果還沒獲取到,會從sharedList中獲取對象
// Otherwise, scan the shared list ... then poll the handoff queue
// 等待獲取連接的線程數(shù)+1
final int waiting = waiters.incrementAndGet();
try {
// 遍歷sharedList
for (T bagEntry : sharedList) {
// 采用CAS方式將獲取的對象狀態(tài)由未使用改為使用中,如果當(dāng)前元素正在使用,則無法修改成功,進入下一循環(huán)
if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
// If we may have stolen another waiter's connection, request another bag add.
if (waiting > 1) {
// 通知監(jiān)聽器添加包元素。如果waiting - addConnectionQueue.size() >= 0,則會讓addConnectionExecutor執(zhí)行PoolEntryCreator任務(wù)
listener.addBagItem(waiting - 1);
}
return bagEntry;
}
}
// 通知監(jiān)聽器添加包元素
listener.addBagItem(waiting);
// 3.如果還沒獲取到,會輪訓(xùn)進入handoffQueue隊列獲取連接對象
timeout = timeUnit.toNanos(timeout);
do {
final long start = currentTime();
// 從handoffQueue隊列中獲取并刪除元素。這是一個無容量的阻塞隊列,插入操作需要阻塞等待刪除操作,而刪除操作不需要等待,如果沒有元素插入,會返回null,如果設(shè)置了超時時間則需要等待
final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
// 這里會出現(xiàn)三種情況,
// 1.超時,返回null
// 2.獲取到元素,但狀態(tài)為正在使用,繼續(xù)執(zhí)行
// 3.獲取到元素,元素狀態(tài)未未使用,修改未使用并返回
if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry;
}
timeout -= elapsedNanos(start);
} while (timeout > 10_000);
// 超時返回null
return null;
}
finally {
// 等待獲取連接的線程數(shù)-1
waiters.decrementAndGet();
}
}
7、HikariCP為什么快?
7.1、通過代碼設(shè)計和優(yōu)化大幅減少線程間的鎖競爭
????????1、元素狀態(tài)的引入,以及使用CAS方法修改狀態(tài)。在ConcurrentBag中,使用使用中、未使用、刪除和保留等表示元素的狀態(tài),而不是使用不同的集合來維護不同狀態(tài)的元素。元素狀態(tài)這一概念的引入非常關(guān)鍵,為后面的幾點提供了基礎(chǔ)。 ConcurrentBag的方法中多處調(diào)用 CAS 方法來判斷和修改元素狀態(tài),這一過程不需要加鎖。
????????2、threadList 的使用。當(dāng)前線程歸還的元素會被綁定到ThreadLocal,該線程再次獲取元素時,在該元素未被偷走的前提下可直接獲取到,不需要去 sharedList 遍歷獲??;
7.2、引入了更多 JDK 的特性
????????尤其是 concurrent 包的工具。相比較于DBCP、C3P0等數(shù)據(jù)庫鏈接池問世較晚,很方便的享受JDK的升級帶來的方便。
????????1、采用CopyOnWriteArrayList來存放元素。在CopyOnWriteArrayList中,讀和寫使用的是不同的數(shù)組,避免了兩者的鎖競爭,至于多個線程寫入,則會加 ReentrantLock 鎖。
????????2、sharedList 的讀寫控制。borrow 和 requite 對 sharedList 來說都是不加鎖的,缺點就是會犧牲一致性。用戶線程無法進行增加元素的操作,只有 addConnectionExecutor 可以,而 addConnectionExecutor 只會開啟一個線程執(zhí)行任務(wù),所以 add 操作不會存在鎖競爭。至于 remove 是唯一會造成鎖競爭的方法,這一點我認(rèn)為也可以參照 addConnectionExecutor 來處理,在加入任務(wù)隊列前把 PoolEntry 的狀態(tài)標(biāo)記為刪除中。
7.3、使用 javassist 直接修改 class 文件生成動態(tài)代理
? ? ? ? 1、使用 javassist 直接修改 class 文件生成動態(tài)代理,精簡了很多不必要的字節(jié)碼,提高代理方法運行速度。尤其JDK1.8優(yōu)化以后JDK的動態(tài)代理,CGlib代理已經(jīng)和javassist、asm等一個數(shù)量級。
8、JDK 、CGLib 、ASM 、Javassist 性能測試
????????環(huán)境:JDK 1.8,CGLib 3.3.0, ASM JDK自帶的ASM包,Javassist 3.26.0-GA。
????????數(shù)據(jù)為執(zhí)行三次,每次調(diào)用5千萬次代理方法的結(jié)果。
1、測試代碼
package cn.zzs.proxy;
import javassist.*;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.FieldVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.DecimalFormat;
/**
* @author lly
**/
public class App {
public static void main(String[] args) throws Exception {
CountService delegate = new CountServiceImpl();
long time = System.currentTimeMillis();
CountService jdkProxy = createJdkDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JDK Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService cglibProxy = createCglibDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create CGLIB Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService javassistProxy = createJavassistDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JAVAASSIST Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService javassistBytecodeProxy = createJavassistBytecodeDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JAVAASSIST Bytecode Proxy: " + time + " ms");
time = System.currentTimeMillis();
CountService asmBytecodeProxy = createAsmBytecodeDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create ASM Proxy: " + time + " ms");
System.out.println("================");
for (int i = 0; i < 3; i++) {
test(jdkProxy, "Run JDK Proxy: ");
test(cglibProxy, "Run CGLIB Proxy: ");
test(javassistProxy, "Run JAVAASSIST Proxy: ");
test(javassistBytecodeProxy, "Run JAVAASSIST Bytecode Proxy: ");
test(asmBytecodeProxy, "Run ASM Bytecode Proxy: ");
System.out.println("----------------");
}
}
private static void test(CountService service, String label)
throws Exception {
service.count(); // warm up
int count = 50000000;
long time = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
service.count();
}
time = System.currentTimeMillis() - time;
System.out.println(label + time + " ms, " + new DecimalFormat().format(count / time * 1000) + " t/s");
}
private static CountService createJdkDynamicProxy(final CountService delegate) {
CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{CountService.class}, new JdkHandler(delegate));
return jdkProxy;
}
private static class JdkHandler implements InvocationHandler {
final Object delegate;
JdkHandler(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object object, Method method, Object[] objects)
throws Throwable {
return method.invoke(delegate, objects);
}
}
private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setInterfaces(new Class[]{CountService.class});
CountService cglibProxy = (CountService) enhancer.create();
return cglibProxy;
}
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(delegate, objects);
}
}
private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(new Class[]{CountService.class});
Class<?> proxyClass = proxyFactory.createClass();
CountService javassistProxy = (CountService) proxyClass.newInstance();
((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));
return javassistProxy;
}
private static class JavaAssitInterceptor implements MethodHandler {
final Object delegate;
JavaAssitInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object self, Method m, Method proceed,
Object[] args) throws Throwable {
return m.invoke(delegate, args);
}
}
private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassPool mPool = new ClassPool(true);
CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");
mCtc.addInterface(mPool.get(CountService.class.getName()));
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));
mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));
Class<?> pc = mCtc.toClass();
CountService bytecodeProxy = (CountService) pc.newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
String className = CountService.class.getName() + "AsmProxy";
String classPath = className.replace('.', '/');
String interfacePath = CountService.class.getName().replace('.', '/');
classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[]{interfacePath});
MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
initVisitor.visitCode();
initVisitor.visitVarInsn(Opcodes.ALOAD, 0);
initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
initVisitor.visitInsn(Opcodes.RETURN);
initVisitor.visitMaxs(0, 0);
initVisitor.visitEnd();
FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);
fieldVisitor.visitEnd();
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");
methodVisitor.visitInsn(Opcodes.IRETURN);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
classWriter.visitEnd();
byte[] code = classWriter.toByteArray();
CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
private static class ByteArrayClassLoader extends ClassLoader {
public ByteArrayClassLoader() {
super(ByteArrayClassLoader.class.getClassLoader());
}
public synchronized Class<?> getClass(String name, byte[] code) {
if (name == null) {
throw new IllegalArgumentException("");
}
return defineClass(name, code, 0, code.length);
}
}
}
2、測試結(jié)果
Create JDK Proxy: 9 ms
Create CGLIB Proxy: 149 ms
Create JAVAASSIST Proxy: 115 ms
Create JAVAASSIST Bytecode Proxy: 58 ms
Create ASM Proxy: 1 ms
================
Run JDK Proxy: 479 ms, 104,384,000 t/s
Run CGLIB Proxy: 541 ms, 92,421,000 t/s
Run JAVAASSIST Proxy: 754 ms, 66,312,000 t/s
Run JAVAASSIST Bytecode Proxy: 194 ms, 257,731,000 t/s
Run ASM Bytecode Proxy: 202 ms, 247,524,000 t/s
----------------
Run JDK Proxy: 404 ms, 123,762,000 t/s
Run CGLIB Proxy: 325 ms, 153,846,000 t/s
Run JAVAASSIST Proxy: 681 ms, 73,421,000 t/s
Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s
Run ASM Bytecode Proxy: 180 ms, 277,777,000 t/s
----------------
Run JDK Proxy: 381 ms, 131,233,000 t/s
Run CGLIB Proxy: 339 ms, 147,492,000 t/s
Run JAVAASSIST Proxy: 674 ms, 74,183,000 t/s
Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s
Run ASM Bytecode Proxy: 181 ms, 276,243,000 t/s
----------------
資料:
動態(tài)代理方案性能對比 - 梁飛的博客 - ITeye博客
GitHub - wwadge/bonecp: BoneCP is a Java JDBC connection pool implementation that is tuned for high performance by minimizing lock contention to give greater throughput for your applications. It beats older connection pools such as C3P0 and DBCP but SHOULD NOW BE CONSIDERED DEPRECATED in favour of HikariCP.
GitHub - brettwooldridge/HikariCP: 光 HikariCP?A solid, high-performance, JDBC connection pool at last.
JDK動態(tài)代理與CGLib動態(tài)代理相關(guān)問題_程序員面試經(jīng)驗分享的博客-CSDN博客
02Hikari源碼解析之ConcurrentBag、FastList分析_concurrentbag解析_一直打鐵的博客-CSDN博客
數(shù)據(jù)庫連接池性能比對(hikari druid c3p0 dbcp jdbc)_c3p0和hikari那個好_把酒問天的博客-CSDN博客
https://www.cnblogs.com/flyingeagle/articles/7102282.html文章來源:http://www.zghlxwxcb.cn/news/detail-499024.html
使用Javassist來動態(tài)創(chuàng)建,修改和代理類 - 算法之名的個人空間 - OSCHINA - 中文開源技術(shù)交流社區(qū)文章來源地址http://www.zghlxwxcb.cn/news/detail-499024.html
到了這里,關(guān)于SpringBoot 默認(rèn)數(shù)據(jù)庫連接池 HikariCP的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!