คำนำ
ก่อนหน้านี้ฉันได้แชร์บทความเกี่ยวกับการใช้ @async เพื่อใช้งานแบบอะซิงโครนัสและการควบคุมพูลเธรดใน Spring Boot: "Spring Boot ใช้ @async เพื่อใช้การโทรแบบอะซิงโครนัส: พูลเธรดที่กำหนดเอง" เนื่องจากฉันเพิ่งค้นพบปัญหามากมายที่เกิดจากการไม่จัดการกับงานแบบอะซิงโครนัสอย่างถูกต้องฉันจะยังคงพูดคุยเกี่ยวกับการปิดสระว่ายน้ำด้ายที่หรูหราส่วนใหญ่สำหรับสระว่ายน้ำด้าย Threadpooltaskscheduler
ปรากฏการณ์ปัญหา
ในตัวอย่างบทที่ 4-1-3 ของบทความก่อนหน้านี้เราได้กำหนดพูลเธรดแล้วเขียน 3 งานโดยใช้คำอธิบายประกอบ @async และระบุพูลเธรดที่ใช้โดยงานเหล่านี้เพื่อดำเนินการ ในการทดสอบหน่วยด้านบนเราไม่ได้พูดคุยเกี่ยวกับปัญหาที่เกี่ยวข้องกับการปิดโดยเฉพาะ มาจำลองปัญหาและเกิดขึ้นในไซต์
ขั้นตอนที่ 1: เหมือนก่อนหน้านี้เรากำหนดพูล Threadpooltaskscheduler Pool:
@springbootapplicationpublic คลาสแอปพลิเคชัน {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {springapplication.run (application.class, args); } @enableasync @configuration class taskpoolconfig {@bean ("taskexecutor") ผู้ดำเนินการสาธารณะ Taskexecutor () {threadpooltaskscheduler executor = new Threadpooltaskscheduler (); Executor.SetPoolSize (20); Executor.SetThreadNamePrefix ("taskexecutor-"); ส่งคืนผู้บริหาร; -ขั้นตอนที่ 2: ปฏิรูปงานอะซิงโครนัสก่อนหน้านี้และทำให้มันพึ่งพาทรัพยากรภายนอกเช่น Redis
@slf4j @componentpublic class task {@autoWired Private Stringredistemplate Stringredistemplate; @async ("taskexecutor") โมฆะสาธารณะ dotaskone () โยนข้อยกเว้น {log.info ("เริ่มงานหนึ่ง"); Long Start = System.currentTimeMillis (); log.info (Stringredistemplate.randomkey ()); Long End = System.currentTimeMillis (); log.info ("งานที่สมบูรณ์หนึ่งเวลาที่ใช้:" + (สิ้นสุด - เริ่มต้น) + "มิลลิวินาที"); } @async ("taskexecutor") โมฆะสาธารณะ dotasktwo () พ่นข้อยกเว้น {log.info ("เริ่มงาน 2"); Long Start = System.currentTimeMillis (); log.info (Stringredistemplate.randomkey ()); Long End = System.currentTimeMillis (); log.info ("งานที่สมบูรณ์ 2 เวลาที่ใช้:" + (สิ้นสุด - เริ่มต้น) + "มิลลิวินาที"); } @async ("taskexecutor") โมฆะสาธารณะ dotaskthree () โยนข้อยกเว้น {log.info ("เริ่มงานสาม"); Long Start = System.currentTimeMillis (); log.info (Stringredistemplate.randomkey ()); Long End = System.currentTimeMillis (); log.info ("งานที่เสร็จสมบูรณ์สาม, ใช้เวลานาน:" + (สิ้นสุด - เริ่มต้น) + "มิลลิวินาที"); -หมายเหตุ: ขั้นตอนในการแนะนำการพึ่งพาและกำหนดค่า REDIS ใน pom.xml ถูกละไว้ที่นี่
ขั้นตอนที่ 3: แก้ไขการทดสอบหน่วยและจำลองสถานการณ์การปิดระบบในการพร้อมกันสูง:
@runwith (SpringJunit4ClassRunner.class) @springboottestpublic คลาสแอปพลิเคชัน {@autowired งานส่วนตัวงาน; @Test @SNEAKYTHROWS การทดสอบโมฆะสาธารณะ () {สำหรับ (int i = 0; i <10,000; i ++) {task.dotaskone (); task.dotasktwo (); task.dotaskthree (); if (i == 9999) {system.exit (0); -หมายเหตุ: โดยการวนรอบสำหรับการวนรอบคุณจะส่งงานไปยังพูลเธรดที่กำหนดไว้ด้านบน เนื่องจากมีการดำเนินการแบบอะซิงโครนัสในระหว่างการดำเนินการให้ใช้ System.Exit (0) เพื่อปิดโปรแกรม ในเวลานี้เนื่องจากมีงานดำเนินการคุณสามารถสังเกตได้ว่าการทำลายงานแบบอะซิงโครนัสเหล่านี้และลำดับของทรัพยากรอื่น ๆ ในภาชนะฤดูใบไม้ผลินั้นปลอดภัยหรือไม่
ขั้นตอนที่ 4: เรียกใช้การทดสอบหน่วยด้านบนและเราจะพบเนื้อหาข้อยกเว้นต่อไปนี้
org.springframework.data.redis.redisconnectionfailureexception: ไม่สามารถรับการเชื่อมต่อเจได ข้อยกเว้นที่ซ้อนกันคือ redis.clients.jedis.exceptions.jedisconnectionException: ไม่สามารถรับทรัพยากรจากกลุ่มที่ org.springframework.data.redis.connection.jedis.jedisconnectionfactory.fetchjedisconnector ~ [Spring-Data-Redis-1.8.10.Release.jar: na] ที่ org.springframework.data.redis.connection.jedis.jedisconnectionfactory.getConnection (jedisconnectionfactory.java:348) org.springframework.data.redis.core.redisconnectionutils.dogetconnection (redisconnectionutils.java:129) ~ [Spring-Data-Redis-1.8.10.release.jar: na] ที่ org.springframework.data.redis.core.edisconnectionuts.getconnection (redisconnectionutils.java:92) ~ [Spring-Data-redis-1.8.10.Release.jar: na] ที่ org.springframework.data.redis.core.redisconnectionUts.getConnection (redisconnectionutils.java:79) ~ [Spring-Data-redis-1.8.10.Release.jar: na] ที่ org.springframework.data.data.data ~ [Spring-Data-Redis-1.8.10.release.jar: na] ที่ org.springframework.data.redis.core.redistemplate.execute (redistemplate.java:169) ~ [ฤดูใบไม้ผลิ org.springframework.data.redis.core.redistemplate.randomkey (redistemplate.java:781) ~ [Spring-Data-Redis-1.8.10.Release.jar: Na] ที่ com.didispace.async.task.dotaskone com.didispace.async.task $$ fassclassbyspringcglib $$ ca3ff9d6.invoke (<penterated>) ~ [คลาส/: na] ที่ org.springframework.cglib.proxy.methodproxy.invoke ~ [Spring-Core-4.3.14.release.jar: 4.3.14.release] ที่ org.springframework.aop.framework.cglibaopproxy $ cglibmethodinvocation.invokejoinpoint (cglibaopproxy.java:738) ~ [Spring-Aop-4.3.14.release.jar: 4.3.14.release] ที่ org.springframework.aop.framework.reflectivemethodinvocation.proceed (rechormivemethodinvocation.java:157) org.springframework.aop.interceptor.asyncexecutionInterceptor $ 1.CALL (ASYNCEXECUTIONINTERECTORECTOR.JAVA:115) ~ [Spring-AOP-4.3.14.RELEASE.JAR: 4.3.14.RELEASE] ที่ Java.Util.Current [NA: 1.8.0_151] ที่ java.util.concurrent.ScheduledThreadPoolexecutor $ scheduledfuturetask.access $ 201 (ScheduledThreadPoolexecutor.java:180) [NA: 1.8.0_151] ที่ java.util.concurrent.scheduledThreadPoolexecutor $ scheduledfuturetask.run (ScheduledThreadPoolexecutor.java:293) [NA: 1.8.0_151] ที่ java.util.Current.threadpoolexecutor.runworker.runworker.runworker. [NA: 1.8.0_151] ที่ java.util.concurrent.threadpoolexecutor $ worker.run (Threadpoolexecutor.java:624) [Na: 1.8.0_151] ที่ java.lang.thread.run (Thread.java:748) redis.clients.jedis.exceptions.jedisconnectionexception: ไม่สามารถรับทรัพยากรจากสระน้ำที่ redis.clients.util.pool.getresource (pool.java:53) ~ [Jedis-2.9.0.jar: na] ที่ redis.clients.jedis.jedispool.getresource ~ [jedis-2.9.0.jar: na] ที่ redis.clients.jedis.jedispool.getresource (jedispool.java:16) ~ [Jedis-2.9.0.jar: na] ที่ org.springframework.data.redis.connection.jedis.jedisconnectionfactory.fetchjedisconnector (jedisconnectionfactory.java:194) ~ [Spring-Data-Redis -1.8.10. java.util.concurrent.locks.abstractqueuedsynchronizer $ conditionobject.reportinterruptafterwait (Abstractqueuedsynchronizer.java:2014) ~ [Na: 1.8.0_151] ที่ java.util.concurrent.locks.abstractqueuedsynchronizer $ conditionobject.awaitnanos (Abstractqueuedsynchronizer.java:2088) ~ [NA: 1.8.0_151] ที่ org.apache.Commons.pool2.Impl.linkedblocking ~ [Commons-Pool2-2.4.3.JAR: 2.4.3] ที่ org.apache.commons.pool2.impl.genericobjectpool.borrowobject (GenericobjectPool.java:442) ~ [Commons-Pool2-2.3.3.jar: 2.4.3] ที่ org.apache.commons.pool2.impl.genericobjectpool.borrowoChect (GenericobjectPool.java:361) ~ [Commons-Pool2-2.4.3.Jar: 2.4.3] ที่ Redis.clients.util.pool.getResource (Pool.java:49)
วิธีแก้ปัญหา
การวิเคราะห์สาเหตุ
จาก息JedisConnectionException: Could not get a resource from the pool มันเป็นเรื่องง่ายสำหรับเราที่จะคิดว่างานอะซิงโครนัสยังคงดำเนินการเมื่อแอปพลิเคชันถูกปิด เนื่องจากพูลการเชื่อมต่อ Redis ถูกทำลายก่อนจึงมีการรายงานข้อผิดพลาดข้างต้นในงานอะซิงโครนัสเพื่อเข้าถึง Redis ดังนั้นเราจึงสรุปได้ว่าวิธีการใช้งานข้างต้นไม่ได้สง่างามเมื่อแอปพลิเคชันถูกปิดดังนั้นเราควรทำอย่างไร?
สารละลาย
มันง่ายมากที่จะแก้ปัญหาข้างต้น Threadpooltaskscheduler ของ Spring ให้การกำหนดค่าที่เกี่ยวข้องกับเราเพียงเพิ่มการตั้งค่าต่อไปนี้:
@Bean ("taskexecutor") ผู้บริหารสาธารณะ Taskexecutor () {threadpooltaskscheduler executor = new Threadpooltaskscheduler (); Executor.SetPoolSize (20); Executor.SetThreadNamePrefix ("taskexecutor-"); Executor.SetWaitFortAskStocompleteOnShutdown (จริง); Executor.SetAwaitTerminationseconds (60); return executor;} หมายเหตุ: setWaitForTasksToCompleteOnShutdown(true) วิธีนี้เป็นคีย์ที่นี่ มันถูกใช้เพื่อตั้งค่าพูลเธรดเพื่อรอให้งานทั้งหมดเสร็จสมบูรณ์ก่อนที่จะทำลายถั่วอื่นต่อไป ด้วยวิธีนี้การทำลายของงานอะซิงโครนัสเหล่านี้จะถูกนำหน้าด้วยการทำลายสระว่ายน้ำด้าย Redis ในเวลาเดียวกัน setawaitterminationseconds (60) ก็ถูกตั้งไว้ที่นี่ วิธีนี้ใช้ในการตั้งค่าเวลารอทำงานในพูลเธรด หากเกินเวลานี้จะถูกบังคับให้ทำลายก่อนที่จะถูกทำลายเพื่อให้แน่ใจว่าแอปพลิเคชันสามารถปิดได้ในตอนท้ายแทนที่จะถูกบล็อก
ตัวอย่างที่สมบูรณ์:
ผู้อ่านสามารถเลือกที่เก็บสองแห่งต่อไปนี้เพื่อดูบทที่ 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