開発プロジェクトでは、通常、非同期タスクを開始する必要があるシナリオがあります。たとえば、ユーザーが正常に登録した場合、いくつかのクーポンを発行する必要があります。現時点では、これらの追加操作がユーザーの登録プロセスに影響を与えないようにするために、通常、スレッドを非同期に開始してクーポン配布ロジックを実行します。
通常は、自分でスレッドプールを定義し、スレッドタスクを開始する必要があります。これはSpringbootで簡略化され、org.springframework.core.task.TaskExecutorタイプのタスクスレッドプールが自動的に構成されます。@ EnableAsyncアノテーションを有効にすると、非同期タスクを実行する必要があるときに、@ Asyncアノテーションを追加します。このメソッド実行するスレッドを自動的に開始します。
非同期タスクを開始するようにSpringbootを構成する方法を説明するときは、Javaスレッドプールの基本的な知識を簡単に説明しましょう。
この記事のナビゲーション
スレッドプールの基礎知識
スレッドプールのシナリオを使用する
通常、同時実行性が高く、タスクの実行時間が短く、スレッドを作成する時間はT1、タスクを実行する時間はT2、スレッドを破棄する時間はT3です。T1+ T2の時間がT2よりはるかに長い場合、スレッドプールを使用できます。コア機能は、スレッドを再利用し、スレッドの作成と破棄にかかる時間のオーバーヘッドを削減することです。
完了したタスクの数などのタスク実行情報を便利にカウントします。
エグゼキュータースレッドプールの最上位インターフェイス
Jdk1.5バージョン以降、開発者が独自のマルチスレッドタスクを簡単に作成できるようにスレッドプールが提供されます。java.util.concurrent.Executorインターフェースは、スレッドプールのトップレベルのインターフェースです。
インターフェースexecuteは、タスクを実行するためのRunnableスレッドインスタンスを受け取ります。
public interface Executor {
void execute(Runnable command);
}
ExecutorServiceインターフェースの詳細な説明
public interface ExecutorService extends Executor {
/**关闭线程池不会接收新的任务,内部正在跑的任务和队列里等待的任务,会执行完
*/
void shutdown();
/**
* 关闭线程池,不会接收新的任务
* 尝试将正在跑的任务interrupt中断
* 忽略队列里等待的任务
* 返回未执行的任务列表
*/
List<Runnable> shutdownNow();
/**
* 返回线程池是否被关闭
*/
boolean isShutdown();
/**
*线程池的任务线程是否被中断
*/
boolean isTerminated();
/**
*接收timeout和TimeUnit两个参数,用于设定超时时间及单位。
* 当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。和shutdown方法组合使用。
*/
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 提交任务会获取线程的执行结果
*/
<T> Future<T> submit(Callable<T> task);
/**
* 提交任务会获取线程的T类型的执行结果
*/
<T> Future<T> submit(Runnable task, T result);
/**
* 提交任务
*/
Future<?> submit(Runnable task);
/**
* 批量提交返回任务执行结果
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
/**
* 批量提交返回任务执行结果
*增加了超时时间控制,这里的超时时间是针对的所有tasks,而不是单个task的超时时间。
*如果超时,会取消没有执行完的所有任务,并抛出超时异常
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
/**
* 取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务
* 任务执行超时抛出异常
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
ThreadPoolExecutorパラメータ分析
ThreadPoolExecutorは、非同期タスクを処理するために使用されるインターフェイスです。これは、スレッドプールおよびタスクキューとして理解できます。ExecutorServiceオブジェクトに送信されたタスクは、タスクチームに入れられるか、スレッドプール内のスレッドによって直接実行されます。ThreadPoolExecutorは調整をサポートしますパラメータを作成して、さまざまなタスク処理戦略を構成します。
ThreadPoolExecutorスレッドプールコンストラクターコードを以下に示します。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutorの各入力パラメーターの意味:パラメーター間の関係:
- スレッドプールから使用可能なスレッドを取得してタスクを実行します。使用可能なスレッドがない場合は、スレッドの数がcorePoolSizeの制限に達するまで、ThreadFactoryを使用して新しいスレッドを作成します
- スレッドプール内のスレッド数がcorePoolSizeに達すると、キューがそれ以上のタスクに対応できなくなるまで、新しいタスクがキューに入れられます
- キューがこれ以上タスクを保持できなくなると、スレッドの数がmaxinumPoolSizeの制限に達するまで新しいスレッドが作成されます
- スレッド数がmaxinumPoolSizeの制限に達すると、新しいタスクは実行を拒否され、RejectedExecutionHandlerが呼び出されて処理されます。
RejectedExecutionHandler拒否戦略
1. AbortPolicy:例外を直接スローする、デフォルトのポリシー;
2. CallerRunsPolicy:呼び出し元のスレッドを使用してタスクを実行する;
3. DiscardOldestPolicy:ブロッキングキューの最初のタスクを破棄して現在のタスクを実行する;
4. DiscardPolicy:直接破棄する仕事;
ThreadPoolExecutorを使用する際のポイント
- スレッドプールのサイズを適切に構成します。CPU集中型のタスクの場合、スレッドプールの数はCPUコアの数+ 1に設定できます。IO集中型のタスクの場合、CPUはアイドル状態がより多く、スレッドプールの数はCPUコアの数に設定されます* 2
- タスクキュー:タスクキューは通常、LinkedBlockingQueueを使用してサイズを指定し、拒否戦略と組み合わせて使用されます。デフォルトはInteger.Max_VALUEであり、メモリオーバーフローが発生しやすい傾向があります。
- タスク例外:try {} catch {}を追加して、スレッドプールのタスクアテンションで例外をキャッチします。これはトラブルシューティングに便利です
SpringBootスレッドプール
org.springframework.core.task.TaskExecutorはSpring非同期スレッドプールのインターフェースクラスであり、その本質はjava.util.concurrent.Executorです。デフォルトでspringboot2.3.xによって使用されるスレッドプールはThreadPoolTaskExecutorであり、TaskExecutionPropertiesを通じて簡単に調整できます。
SpringBootは独自の非同期スレッドプールを定義します
@EnableAsync
@Component
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(8);
// 设置最大线程数
executor.setMaxPoolSize(16);
// 设置队列容量
executor.setQueueCapacity(50);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("CodehomeAsyncTask-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
public Executor getAsyncExecutor() {
return taskExecutor();
}
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncExceptionHandler();
}
/**
* 自定义异常处理类
*/
class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
log.info("Exception message - " + throwable.getMessage());
log.info("Method name - " + method.getName());
for (Object param : objects) {
log.info("Parameter value - " + param);
}
}
}
}
使用する2つの方法
@Component
@Slf4j
public class UserServiceSyncTask {
//不带返回值
@Async
public void sendEmail(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread().getName());
}
//带返回值
@Async
public Future<String> echo(String msg){
try {
Thread.sleep(5000);
return new AsyncResult<String>(Thread.currentThread().getName()+"hello world !!!!");
} catch (InterruptedException e) {
//
}
return null;
}
}
千マイルは単一のステップから始まります。これはSpringBootチュートリアルシリーズの10番目の記事で、すべてのプロジェクトソースコードはGitHubからダウンロードできます。