序文
私は以前、@asyncを使用して非同期タスクとスレッドプールコントロールをSpring Bootに実装することに関する記事を共有しました。私は最近、非同期タスクを正しく処理しないことによって引き起こされる多くの問題を発見したので、主にThreadPooltasksChedulerスレッドプールのスレッドプールのエレガントな閉鎖についても話し続けます。
問題現象
前の記事の第4-1-3章の例では、スレッドプールを定義し、@Asyncアノテーションを使用して3つのタスクを作成し、これらのタスクで使用するスレッドプールを実行して実行しました。上記のユニットテストでは、シャットダウン関連の問題については特に話しませんでした。問題をシミュレートして、現場でそれを考え出しましょう。
ステップ1:以前と同様に、スレッドプールタスクシェードラースレッドプールを定義します。
@springBootApplicationPublic class Application {public static void main(string [] args){springApplication.run(application.class、args); } @enableasync @configuration class taskpoolconfig {@bean( "taskexecutor")Public Executor taskexecutor(){threadpooltaskscheduler executor = new SthreadPooltasksCheduler(); executor.setpoolsize(20); executor.setThreadNamePrefix( "taskexecutor-");執行者を返す; }}}ステップ2:以前の非同期タスクを改革し、Redisなどの外部リソースに依存させる
@slf4j @componentpublic class task {@autowired private stringredistemplate stringredistemplate; @async( "taskexecutor")public void dotaskone()throws exception {log.info( "start task one"); long start = system.currenttimemillis(); log.info(stringredistemplate.randomkey()); long end = system.currenttimemillis(); log.info( "タスク1の完了、時間が取られた時間:" +(end -start) + "milliseconds"); } @async( "taskexecutor")public void dotasktwo()throws exception {log.info( "start task 2"); long start = system.currenttimemillis(); log.info(stringredistemplate.randomkey()); long end = system.currenttimemillis(); log.info( "完全なタスク2、時間を取る時間:" +(end -start) + "milliseconds"); } @async( "taskexecutor")public void dotaskthree()throws exception {log.info( "start task 3"); long start = system.currenttimemillis(); log.info(stringredistemplate.randomkey()); long end = system.currenttimemillis(); log.info( "タスク3の完了、時間消費:" +(end -start) + "milliseconds"); }}注: POM.xmlで依存関係を導入してRedisを構成する手順は省略されています
ステップ3:ユニットテストを変更し、高い並行性でシャットダウンの状況をシミュレートします。
@runwith(springjunit4classrunner.class)@springboottestpublic class applicationtests {@autowired private task task; @test @sneakythrows public void test(){for(int i = 0; i <10000; i ++){task.dotaskone(); task.dotasktwo(); task.dotaskthree(); if(i == 9999){system.exit(0); }}}}注:forループをループすることにより、上記のスレッドプールにタスクを送信します。非同期に実行されるため、実行中にSystem.Exit(0)を使用してプログラムを閉じます。この時点で、タスクが実行されているため、これらの非同期タスクの破壊とスプリングコンテナ内の他のリソースの順序が安全かどうかを観察できます。
ステップ4:上記のユニットテストを実行すると、次の例外コンテンツが発生します。
org.springframework.data.redis.redisconnectionFailureException:Jedis接続を取得できません。ネストされた例外はredis.clients.jedis.exceptions.jedisconnectionexception:org.springframework.data.redis.connection.jedisconnectionfactoryのプールからリソースを取得できませんでしたorg.springframework.data.redis.connection.jedis.jedisconnectionfactory.getConnection(jedisconnectionfactory.java:348)〜[spring-data-redis-1.8.10.Release.jar:na] at org.springframework.data.redis.core.redisconnectionutils.dogetconnection(redisconnectionutils.java:129)〜[spring-data-redis-1.8.10.release.jar:na] at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79) 〜[spring-data-redis-1.8.10.release.jar:na] at org.springframework.data.redis.core.redistemplate.execute(redistemplate.java:194)〜 org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisTemplate.randomKey(RedisTemplate.java:781) 〜[spring-data-redis-1.8.10.Release.jar:na] com.didispace.async.task.dotaskone(task.java:26)〜[classes/:na] at com.didispace.async.task $$ fassclasbyspringcglib $ ca3ff9d6.invoke(< org.springframework.cglib.proxy.methodproxy.invoke(methodproxy.java:204)〜[Spring-Core-4.3.14.Release.jar:4.3.14.Release] at org.springframework.aop.framework.cglibaopproxy $ cglibmethodinvocation.invokejoinpoint(cglibaopproxy.java:738)〜 org.springframework.aop.framework.ReftiveMethodInvocation.proceed(refrectivemethodinvocation.java:157)〜[spring-aop-4.3.14.release.jar:4.3.14.release] at org.springframework.aop.interceptor.asyncexecutioninterceptor $ 1.call(asyncexecutioninterceptor.java:115)〜[spring-aop-4.3.14.release.jar:4.3.14.release] [Na:1.8.0_151] at java.util.concurrent.scheduledthreadpoolexecutor $ scheduledfuturetask.access $ 201(Scheduledthreadpoolexecutor.java:180)[Na:1.8.0_151] at java.util.concurrent.scheduledthreadpoolexecutor $ scheduledfuturetask.run(scheduledthreadpoolexecutor.java:293)[na:1.8.0_151] at java.util.concurrent.threadpoolexecutor.runworker(java:1149) [NA:1.8.0_151] at java.util.concurrent.threadpoolexecutor $ worker.run(shoodpoolexecutor.java:624)[na:1.8.0_151] at java.lang.thread.run(thread.java:748)[Na:1.8.0_151] by: Redis.clients.jedis.exceptions.jedisconnectionexception:redis.clients.util.pool.getResource(pool.java:53)〜[jedis-2.0.jar:na] atredis.clients.jedis.jedispool.getresource(jedispool.java:226のリソースを取得できませんでした。 〜[jedis-2.9.0.jar:na] at redis.clients.jedis.jedispool.getResource(jedispool.java:16)〜[jedis-2.9.0.jar:na] at springframework.data.redis.connection.jedis.jedisconnectionfactory.fetchjedisconnector(jedisconnectionfactory.java:194)〜[spring-data-redis-1.8.10.release.jar:na] ... java.util.concurrent.locks.abstractqueuedsynchronizer $ conditionobject.Reportinterruptafterwait(abstractqueuedsynchronizer.java:2014)〜[na:1.8.0_151] at java.util.concurrent.locks.abstractqueuedsynchronizer $ conditionobject.awaitnanos(abstractqueuedsynchronizer.java:2088)〜[na:1.8.0_151] 〜[commons-pool2-2.4.3.jar:2.4.3] at org.apache.commons.pool2.impl.genericobjectpool.borrobject(genericobjectpool.java:442)〜[commons-pool2-2.4.3.jar:2.4.3] at org.apache.commons.pool2.impl.genericobjectpool.borrobject(genericobjectpool.java:361)〜[Commons-Pool2-2.4.3.jar:2.4.3]
それを解決する方法
原因分析
例外息JedisConnectionException: Could not get a resource from the pool 、アプリケーションが閉じたときに非同期タスクがまだ実行されていると考えるのは簡単です。 Redis接続プールが最初に破壊されるため、上記のエラーは非同期タスクで報告され、Redisにアクセスします。したがって、アプリケーションが閉じたときに上記の実装方法はエレガントではないと結論付けました。
解決
上記の問題を解決するのは非常に簡単です。 SpringのThreadPoolTasksChedulerは、関連する構成を提供します。次の設定を追加するだけです。
@bean( "taskexecutor")Public Exector taskexecutor(){threadPooltaskscheduler executor = new SthreadPooltasksCheduler(); executor.setpoolsize(20); executor.setThreadNamePrefix( "taskexecutor-"); executor.setWaitfortAskStocompleteOnshutdown(true); executor.setawaitterminationseconds(60);執行者を返す;}注: setWaitForTasksToCompleteOnShutdown(true)この方法はここで重要です。他の豆を破壊し続ける前に、すべてのタスクが完了するのを待つためにスレッドプールを設定するために使用されます。このようにして、これらの非同期タスクの破壊は、Redisスレッドプールの破壊によって先行されます。同時に、SetAwaitterminationseconds(60)もここに設定されています。この方法は、スレッドプールにタスクの待ち時間を設定するために使用されます。この時間を超えた場合、アプリケーションがブロックされるのではなく最終的に閉鎖できるように、破壊される前に破壊することを余儀なくされます。
完全な例:
読者は、次の2つのリポジトリを選択して、選好に応じて第4章1-4プロジェクトを表示できます。
github:https://github.com/dyc87112/springboot-learning/
gitee:https://gitee.com/diidispace/springboot-learning/
ローカルダウンロード:http://xiazai.vevb.com/201805/yuanma/springboot-learning(vevb.com).rar
要約します
上記は、この記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。