最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

多線程下嵌套異步任務導致程序假死問題

 更新時間:2024年08月20日 10:51:28   作者:xiaolyuh123  
這篇文章主要介紹了多線程下嵌套異步任務導致程序假死問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

問題描述

線上環(huán)境異步任務全部未執(zhí)行,代碼沒有拋出任何異常和提示,CPU、內(nèi)存都很正常,基本沒有波動,GC也沒啥異常的。

問題原因

經(jīng)定位是異步由于嵌套異步任務使用了Future.get()方法導致的程序阻塞

手動使用線程池示例

public class FutureBlockTest {
    public static void main(String[] args) {
        // 為了模擬我這里只存創(chuàng)建一個工作線程
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
        // 第一層異步任務
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName() + "-main-thread");
            // 第二層異步任務(嵌套任務)
            FutureTask<Long> futureTask = new FutureTask<>(() -> {
                System.out.println(Thread.currentThread().getName() + "-child-thread");
                return 10L;
            });
            fixedThreadPool.execute(futureTask);
            System.out.println("子任務提交完畢");

            // 獲取子線程的返回值
            try {
                System.out.println(futureTask.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
        // 提交主線
        fixedThreadPool.submit(runnable);
    }
}

執(zhí)行上訴示例后輸出

pool-1-thread-1-main-thread
子任務提交完畢

然后程序假死。

使用@Async示例

// 程序入口
@Controller
public class AsyncController {
    @Autowired
    private MainThreadService mainThreadService;

    @GetMapping("/")
    public String helloWorld() throws Exception {
        mainThreadService.asyncMethod();
        return "Hello World";
    }
}

// 主任務代碼
@Service
public class MainThreadService {
    @Autowired
    private ChildThreadService childThreadService;

    @Async("asyncThreadPool")
    public void asyncMethod() throws Exception {
        // 主任務開始
        // TODO
        // 開啟子任務
        Future<Long> longFuture = childThreadService.asyncMethod();
        // 子任務阻塞子任務
        longFuture.get();
        // TODO
    }
}
// 子任務示例
@Service
public class ChildThreadService {
    @Async("asyncThreadPool")
    public Future<Long> asyncMethod() throws Exception {
        // 子任務執(zhí)行
        Thread.sleep(1000);
        // 返回異步結(jié)果
        return new AsyncResult<>(10L);
    }
}

定位

1.通過jpsjstack命令定位

jstack 81173 | grep 'WAITING' -A 15

admin@wangyuhao spring-boot-student % jstack 81173 | grep 'WAITING' -A 15
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076b541b38> (a java.util.concurrent.FutureTask)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
        at java.util.concurrent.FutureTask.get(FutureTask.java:191)
        at com.xiaolyuh.FutureBlockTest.lambda$main$1(FutureBlockTest.java:28)
        at com.xiaolyuh.FutureBlockTest$$Lambda$1/885951223.run(Unknown Source)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
        at java.util.concurrent.FutureTask.run(FutureTask.java)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

可以定位到是futureTask.get()發(fā)生了阻塞。

2.也可以使用 Arthas定位

狀態(tài)場景原因
BLOCKED線程處于BLOCKED狀態(tài)的場景1.當前線程在等待一個monitor lock,比如synchronizedhuo或者Lock。
WAITING線程處于WAITING狀態(tài)的場景1. 調(diào)用Object對象的wait方法,但沒有指定超時值。
2. 調(diào)用Thread對象的join方法,但沒有指定超時值。
3. 調(diào)用LockSupport對象的park方法。
TIMED_WAITING線程處于TIMED_WAITING狀態(tài)的場景1. 調(diào)用Thread.sleep方法。
2. 調(diào)用Object對象的wait方法,指定超時值。
3. 調(diào)用Thread對象的join方法,指定超時值。
4. 調(diào)用LockSupport對象的parkNanos方法。
5. 調(diào)用LockSupport對象的parkUntil方法。

問題分析

線程池內(nèi)部結(jié)構(gòu)

當線程1中的任務A嵌套了任務C后,任務C被放到了阻塞隊列,這時線程1就被柱塞了,必須等到任務C執(zhí)行完畢。

這時如果其他線程也發(fā)生相同清空,如線程2的任務B,他的嵌套任務D也被放入阻塞隊列,這是線程2也會被阻塞。

如果這類任務比較多時就會將所有線程池的線程阻塞住。最后導致線程池假死,所有異步任務無法執(zhí)行。

解決辦法

  • futureTask.get()必須加上超時時間,這樣至少不會導致程序一直假死
  • 不要使用嵌套的異步任務,或者嵌套任務不要獲取子任務結(jié)果,不要阻塞主任務
  • 將主任務和子任務的線程池拆分成兩個線程池池,不要使用同一個線程池(推薦)

思考

我們程序代碼使用的@Async注解,也就是示例二的代碼。使用注解默認配置,那么Spring會給所有任務分配單獨線程,且線程不能重用,源碼如下:

獲取Executor源碼

org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor

	/**
	 * This implementation searches for a unique {@link org.springframework.core.task.TaskExecutor}
	 * bean in the context, or for an {@link Executor} bean named "taskExecutor" otherwise.
	 * If neither of the two is resolvable (e.g. if no {@code BeanFactory} was configured at all),
	 * this implementation falls back to a newly created {@link SimpleAsyncTaskExecutor} instance
	 * for local use if no default could be found.
	 * @see #DEFAULT_TASK_EXECUTOR_BEAN_NAME
	 */
	@Override
	protected Executor getDefaultExecutor(BeanFactory beanFactory) {
		Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
		return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
	}

獲取執(zhí)行任務源碼

org.springframework.core.task.SimpleAsyncTaskExecutor#doExecute

	/**
	 * Template method for the actual execution of a task.
	 * <p>The default implementation creates a new Thread and starts it.
	 * @param task the Runnable to execute
	 * @see #setThreadFactory
	 * @see #createThread
	 * @see java.lang.Thread#start()
	 */
	protected void doExecute(Runnable task) {
		Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
		thread.start();
	}

我們可以發(fā)現(xiàn)默認執(zhí)行@Async注解的異步線程池,內(nèi)部其實就沒用線程池,它會給每一個任務創(chuàng)建一個新的線程,線程使用過后會銷毀掉,線程不會重用。

  • 那它將會帶來一個問題,那就是異步任務過多就會不斷創(chuàng)建線程,最終將系統(tǒng)資源耗盡。
  • 這也是網(wǎng)絡(luò)上大部分文章不推薦直接使用@Async注解默認配置的原因。

我們需要思考的是,Spring的設(shè)計這為什么要這樣設(shè)計,這里有這么明顯的問題,難道他們不知道嗎,我理解這樣設(shè)計的初衷可能就是為了避免上訴我們發(fā)現(xiàn)的任務嵌套問題,因為每個任務單獨線程執(zhí)行是不會發(fā)生上訴程序假死的情況的。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論

闽清县| 阳原县| 黔江区| 大洼县| 舟山市| 大洼县| 桐梓县| 咸丰县| 玉田县| 随州市| 图们市| 新建县| 会宁县| 马鞍山市| 郴州市| 新昌县| 年辖:市辖区| 厦门市| 巴东县| 双峰县| 冷水江市| 安平县| 盖州市| 枣强县| 建平县| 陆河县| 兖州市| 宾阳县| 阳新县| 延长县| 依兰县| 工布江达县| 湘阴县| 富裕县| 吉木乃县| 德州市| 清原| 台中县| 都匀市| 安丘市| 吐鲁番市|