1. HikariPoolでのワーカースレッドスケジューリング
HikariPoolの作業スレッドはThreadPoolExecutorを介してスケジュールされ、合計3つのThreadPoolExecutorインスタンスがあります。
ThreadPoolExecutor | 責任 | 過負荷後の治療戦略 |
---|---|---|
houseKeepingExecutorService | 責任 1.データベース接続プールの動的スケーリングでデータベース接続を削減する 2.データベース接続リークを 監視する3.最大寿命を超えてデータベース接続を監視する |
放棄する |
addConnectionExecutor | データベース接続プールの動的なスケーリング時に新しいデータベース接続を追加するなど、データベース接続の作成を担当します。 | 放棄する |
closeConnectionExecutor | データベース接続を閉じる責任があります。 | 成功するまで実行を繰り返す |
1.1。houseKeepingExecutorService
インスタンス化:
//HikariPool.java
private ScheduledExecutorService initializeHouseKeepingExecutorService()
{
if (config.getScheduledExecutor() == null) {
final ThreadFactory threadFactory = Optional.ofNullable(config.getThreadFactory()).orElseGet(() -> new DefaultThreadFactory(poolName + " housekeeper", true));
final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
executor.setRemoveOnCancelPolicy(true);
return executor;
}
else {
return config.getScheduledExecutor();
}
}
复制代码
1.1.1接続リークの監視
//HikariPool.java
this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
复制代码
//ProxyLeakTaskFactory.java
private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
ProxyLeakTask task = new ProxyLeakTask(poolEntry);
// executorService就是houseKeepingExecutorService
task.schedule(executorService, leakDetectionThreshold);
return task;
}
复制代码
//ProxyLeakTask.java
ProxyLeakTask(final PoolEntry poolEntry)
{
this.exception = new Exception("Apparent connection leak detected");
this.threadName = Thread.currentThread().getName();
this.connectionName = poolEntry.connection.toString();
}
public void run()
{
isLeaked = true;
final StackTraceElement[] stackTrace = exception.getStackTrace();
final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
System.arraycopy(stackTrace, 5, trace, 0, trace.length);
exception.setStackTrace(trace);
// 下面是监控到连接泄漏的处理,这里只是记录到日志中,如果通过一个接口处理,并可以让使用者动态实现会更灵活
LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception);
}
复制代码
1.1.2接続プールの動的スケーリング
//HikariPool.java
// HouseKeeper是负载连接池动态伸缩的工作线程
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
复制代码
1.1.3データベース接続の最大存続期間の監視
final long maxLifetime = config.getMaxLifetime();
if (maxLifetime > 0) {
// variance up to 2.5% of the maxlifetime
final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
final long lifetime = maxLifetime - variance;
poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
() -> {
if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
addBagItem(connectionBag.getWaitingThreadCount());
}
},
lifetime, MILLISECONDS));
}
复制代码
1.2。addConnectionExecutor
インスタンス化:
//HikariPool.java
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
复制代码
//UtilityElf.java
public static ThreadPoolExecutor createThreadPoolExecutor(final BlockingQueue<Runnable> queue, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy)
{
if (threadFactory == null) {
threadFactory = new DefaultThreadFactory(threadName, true);
}
ThreadPoolExecutor executor = new ThreadPoolExecutor(1 /*core*/, 1 /*max*/, 5 /*keepalive*/, SECONDS, queue, threadFactory, policy);
executor.allowCoreThreadTimeOut(true);
return executor;
}
复制代码
接続を追加:
//HikariPool.java
public void addBagItem(final int waiting)
{
final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
if (shouldAdd) {
addConnectionExecutor.submit(poolEntryCreator);
}
}
// 连接词动态伸缩增加连接
private synchronized void fillPool()
{
final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
- addConnectionQueue.size();
for (int i = 0; i < connectionsToAdd; i++) {
addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
}
}
复制代码
1.3。closeConnectionExecutor
インスタンス化:
//HikariPool.java
this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
复制代码
//UtilityElf.java
public static ThreadPoolExecutor createThreadPoolExecutor(final int queueSize, final String threadName, ThreadFactory threadFactory, final RejectedExecutionHandler policy)
{
if (threadFactory == null) {
threadFactory = new DefaultThreadFactory(threadName, true);
}
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(queueSize);
ThreadPoolExecutor executor = new ThreadPoolExecutor(1 /*core*/, 1 /*max*/, 5 /*keepalive*/, SECONDS, queue, threadFactory, policy);
executor.allowCoreThreadTimeOut(true);
return executor;
}
复制代码
接続を閉じます。
void closeConnection(final PoolEntry poolEntry, final String closureReason)
{
if (connectionBag.remove(poolEntry)) {
final Connection connection = poolEntry.close();
closeConnectionExecutor.execute(() -> {
quietlyCloseConnection(connection, closureReason);
if (poolState == POOL_NORMAL) {
fillPool();
}
});
}
}
复制代码
2.関連ツール
クラス | 責任 |
---|---|
ThreadPoolExecutor | スレッドエグゼキュータ |
BlockingQueue | スレッドプールによって使用されるバッファキュー。キューの長さは、バッファリングできるワーカースレッドの最大数を決定します |
ThreadFactory | ワーカースレッドのスレッドファクトリを作成する |
ScheduledThreadPoolExecutor | スケジュールされたスケジューリングをサポートするスレッドプールエグゼキューターは、遅延実行と定期実行を指定できます。このようにして、遅延時間を最大寿命に設定して、データベース接続が最大寿命を超えているかどうかを監視できます。 |
DefaultThreadFactory | HikariPoolに実装されているデフォルトのスレッドファクトリは、スレッド名を設定し、そのスレッドをスプライトスレッドとして設定します。 |
RejectedExecutionHandler | スレッドエグゼキューターのスレッドキューがいっぱいになったときに新しいスレッドを追加するための処理戦略インターフェイス |
DiscardOldestPolicy | スレッドキュー内で最も古い未実行のワーカースレッドを破棄し、HikariPoolで使用されていない新しいワーカースレッドを追加します。 |
CallerRunsPolicy | 成功するまで実行を繰り返し、closeConnectionExecutorで使用します。 |
AbortPolicy | スレッドキューの負荷を超えるワーカースレッドを破棄し、例外をスローします。ひかりプールでは使用しません。 |
DiscardPolicy | スレッドキューを超えて何もしない複雑なワーカースレッドを無視します。houseKeepingExecutorServiceおよびhouseKeepingExecutorServiceで使用されます。 |
3.コアクラス
3.1 ThreadPoolExecutor
コンストラクタ:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
复制代码
パラメータの説明:
パラメータ | 解説 |
---|---|
int corePoolSize | スレッドプールエグゼキューターに保持するスレッドの最小数 |
int maximumPoolSize | スレッドプールエグゼキューターが許可するスレッドの最大数 |
長いkeepAliveTime | 未使用スレッドの保持時間。この時間を超え、スレッド数が最小スレッド数より大きい場合、スレッドは解放されます |
TimeUnitで | 未使用スレッド保持時間単位 |
BlockingQueue | スレッドバッファキュー。このキューの効果はmaximumPoolSizeの影響を受け、スレッド数が十分な場合はキューに入れられません。 |
ThreadFactory | ワーカースレッドを生成するためのスレッドファクトリインターフェイス |
RejectedExecutionHandler | ワーカースレッドの数がキャッシュキューに配置された後の処理戦略インターフェイスは、キャッシュキューの容量を超えています |
ここで注意すべき点がいくつかあります。
- スレッドバッファーキューは、無限の増加によるメモリオーバーフローを回避するために、制限付きキューとして設定する必要があります。
- 上記と同じ理由で、スレッドの最大数もInteger.MAX_VALUEに設定しないように適切に制御する必要があります。
- スレッドバッファーキューの処理ロジックは、corePoolSizeおよびmaximumPoolSizeの影響を受けます。簡単に言うと、十分な使用可能なスレッドがある場合、ワーカースレッドはスレッドバッファーキューに入れられません。
例:
import java.util.concurrent.*;
import static java.util.concurrent.ThreadPoolExecutor.*;
public class ThreadPoolExecutorTest {
private static int runableNum = 1;
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue(3);
// 修改maximumPoolSize和maximumPoolSize的大小可以看到对queue处理逻辑的影响
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 300, TimeUnit.SECONDS,
queue, new DefaultThreadFactory(), new DefaultDiscardPolicy());
while(true) {
System.out.println("runableNum: " + runableNum);
executor.execute(new DefaultRunnable("id-" + runableNum));
runableNum++;
quietlySleep(500);
}
}
private static void quietlySleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
private static class DefaultRunnable implements Runnable {
private String name;
public DefaultRunnable(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("Runnable-" + name + " run.");
quietlySleep(3000);
}
public String getName() {
return this.name;
}
}
private static class DefaultDiscardPolicy extends DiscardPolicy {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
super.rejectedExecution(r, e);
if (r instanceof DefaultRunnable) {
DefaultRunnable defaultRunnable = (DefaultRunnable)r;
System.out.println("Runnable-" + defaultRunnable.getName() + " be discard.");
}
}
}
private static class DefaultThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
}
}
复制代码
出力:
runableNum: 1
Runnable-id-1 run.
runableNum: 2
runableNum: 3
runableNum: 4
runableNum: 5
Runnable-id-5 run.
runableNum: 6
Runnable-id-6 run.
Runnable-id-2 run.
runableNum: 7
runableNum: 8
Runnable-id-8 be discard.
runableNum: 9
Runnable-id-9 be discard.
runableNum: 10
Runnable-id-10 be discard.
Runnable-id-3 run.
runableNum: 11
Runnable-id-4 run.
runableNum: 12
Runnable-id-7 run.
runableNum: 13
runableNum: 14
Runnable-id-14 be discard.
复制代码
3.2 ScheduledThreadPoolExecutor
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
复制代码
スレッドの実行を遅らせて接続リークを監視したり、最大寿命を超えたりする機能を借りることができます。
4.まとめ
- ThreadPoolExecutorおよびScheduledThreadPoolExecutor関連クラスは、スレッド実行パフォーマンスを向上させるためのコアツールクラスであり、適切に使用する必要があります。
- スレッドツールを最大限に活用してリソースプールを管理し、作業スレッドを合理的に配置します。
終わり。
<-左側のように注意を払って、トリプルコンボをありがとう。
関連
資料:HikariPoolソースコード(1)
HikariPoolソースコードを理解する(2)
HikariPoolソースコードから借用した設計アイデア(3)リソースプールの動的スケーリング
HikariPoolソースコード(4)リソースステータス
HikariPoolソースコード(6)いくつかの便利なJAVA機能
Javaオタクサイト: javageektour.com/