国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Spring框架下向異步線程傳遞HttpServletRequest參數(shù)的坑

王笑朝 / 971人閱讀

摘要:在中設(shè)置為后發(fā)送請求,可以看到結(jié)果顯然,誰也沒辦法保證自己出來的子線程會先于主線程結(jié)束,所以直接傳遞參數(shù)給子線程是不可行的。例如按照為發(fā)送請求后得到這里讓子線程等待秒,以便主線程有充分的時間將賦值為。

在spring的注解 @RequestMapping 之下可以直接獲取 HttpServletRequest 來獲得諸如request header等重要的請求信息:

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
  
    private static final String HEADER = "app-version";
  
    @RequestMapping(value = "/async", method = RequestMethod.GET)
    public void test(HttpServletRequest request) {
                request.getHeader(HEADER);
    }
}

往往,這些重要的信息也會在異步線程中被使用到。于是,一個很自然的想法是,那不如直接把這里獲取到的request當(dāng)做參數(shù)傳到其它spawn出的子線程里,比如:

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
  
    private static final String HEADER = "app-version";
  
    @RequestMapping(value = "/async", method = RequestMethod.GET)
    public void test(HttpServletRequest request) {
        log.info("Main thread: " + request.getHeader(HEADER));      
        new Thread(() -> {
            log.info("Child thread: " + request.getHeader(HEADER));
        }).start();
    }
}

在header中設(shè)置"app-version"為1.0.1后發(fā)送 /test/async 請求,可以看到結(jié)果:

Main thread: 1.0.1

Child thread: 1.0.1

但是,坑,也就此出現(xiàn)了。

由于 HttpServletRequest 不是線程安全的(后知后覺),當(dāng)主線程完成自己的工作返回response后,相應(yīng)的諸如 HttpServletRequest 等對象就會被銷毀。為了看到這個現(xiàn)象,我們可以在子線程中多等待一段時間來保證主線程先于子線程結(jié)束。

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    private static final String HEADER = "app-version";
    private static final long CHILD_THREAD_WAIT_TIME = 5000;

    @RequestMapping(value = "/async", method = RequestMethod.GET)
    public void test(HttpServletRequest request) {
        log.info("Main thread: " + request.getHeader(HEADER));

        new Thread(() -> {
            try {
                Thread.sleep(CHILD_THREAD_WAIT_TIME);
            } catch (Throwable e) {

            }
            log.info("Child thread: " + request.getHeader(HEADER));
        }).start();
    }
}

在header中設(shè)置"app-version"為1.0.1后發(fā)送 /test/async 請求,可以看到結(jié)果:

Main thread: 1.0.1

Child thread: null

顯然,誰也沒辦法保證自己spawn出來的子線程會先于主線程結(jié)束,所以直接傳遞 HttpServletRequest 參數(shù)給子線程是不可行的。

網(wǎng)上有一種方法是通過spring框架自帶的 RequestContextHolder 來獲取request,這對異步線程來講是不可行的。因為只有在負責(zé)request處理的線程才能調(diào)用到 RequestContextHolder 對象,其它線程中它會直接為空。

那么,一個可以想到的笨辦法是將request的值取出來,注入到自定義的對象中,然后將這個對象作為參數(shù)傳遞給子線程:

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    private static final String HEADER = "app-version";
    private static final long MAIN_THREAD_WAIT_TIME = 0;
    private static final long CHILD_THREAD_WAIT_TIME = 5000;

    @RequestMapping(value = "/async", method = RequestMethod.GET)
    public void test(HttpServletRequest request) {
        log.info("Main thread: " + request.getHeader(HEADER));
        TestVo testVo = new TestVo(request.getHeader(HEADER));

        new Thread(() -> {
            try {
                Thread.sleep(CHILD_THREAD_WAIT_TIME);
            } catch (Throwable e) {

            }
            log.info("Child thread: " + request.getHeader(HEADER) + ", testVo = " + testVo.getAppVersion());
        }).start();

        try {
            Thread.sleep(MAIN_THREAD_WAIT_TIME);
        } catch (Throwable e) {

        }
    }

    @Data
    @AllArgsConstructor
    public static class TestVo {
        private String appVersion;
    }
}

再按照"app-version"為1.0.1發(fā)送請求后得到:

Main thread: 1.0.1

Child thread: null, testVo = 1.0.1

嗯,終于成功了。

故事似乎到此就結(jié)束了,但如果仔細考察細節(jié)的話,有幾個問題是值得思考的:

如果child thread中的request已經(jīng)被銷毀了,為什么沒有報null exception,而只是獲取到空的"app-version"的值?

如果request被銷毀了,TestVo這個同樣在主線程中創(chuàng)建的object為什么沒有被銷毀?

主線程真的可以銷毀對象嗎?銷毀對象不是GC負責(zé)的嗎,為什么總是可以在child thread中得到null的結(jié)果?

一個合理的推理是:主線程結(jié)束時,調(diào)用了一個 destroy() 方法,這個方法主動將 HttpServletRequest 中的資源釋放,例如調(diào)用了存放header的map對應(yīng)的 clear() 方法。如此,在子線程中便無法獲得之前的"app-version"所對應(yīng)的value了。而TestVo由于是用戶自己創(chuàng)建,必然不可能實現(xiàn)在 destroy() 方法中寫出釋放資源的代碼。它的值也就保存下來了。

另外,無論主線程是否調(diào)用了 destroy() 方法,真正回收的時候還是GC的工作,這也就解釋了在子線程中不是報null exception,而只是取不到特定的key所對應(yīng)的值。

進一步,我們還可以思考的問題是,為什么在主線程的 destoy() 方法中,不直接將request對象賦值為null呢?

這個問題看似有些蹊蹺,而實則根本不成立。因為就算你把主線程的request變量賦值為null時,子線程中的另一個變量已經(jīng)指向了這個request對應(yīng)的內(nèi)存,依舊可以拿到相應(yīng)的值。例如:

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    private static final String HEADER = "app-version";
    private static final long MAIN_THREAD_WAIT_TIME = 5000;
    private static final long CHILD_THREAD_WAIT_TIME = 3000;

    @RequestMapping(value = "/async", method = RequestMethod.GET)
    public void test(HttpServletRequest request) {
        log.info("Main thread: " + request.getHeader(HEADER));
        TestVo testVo = new TestVo(request);

        new Thread(() -> {
            try {
                Thread.sleep(CHILD_THREAD_WAIT_TIME);
            } catch (Throwable e) {

            }
            log.info("Child thread: " + testVo.getRequest().getHeader(HEADER));
        }).start();

        request = null;

        try {
            Thread.sleep(MAIN_THREAD_WAIT_TIME);
        } catch (Throwable e) {

        }
    }

    @Data
    @AllArgsConstructor
    public static class TestVo {
        private HttpServletRequest request;
    }
}

按照"app-version"為1.0.1發(fā)送請求后得到:

Main thread: 1.0.1

Child thread: 1.0.1

這里讓子線程等待3秒,以便主線程有充分的時間將request賦值為null。但child線程依舊可以拿到對應(yīng)的值。

所以,將request變量賦值為null根本無法做到釋放資源。所以對request里保存header的map來講,將變量賦值為null無法保證其它地方的引用也會一并消失。最直接有效的方法是調(diào)用 clear() 讓map中的每一個元素失效。

所以總結(jié)起來是:

主線程的request和testVo,由于都有子線程的變量指向,也即是兩個對象上的reference count不為0,GC便不會真正回收這兩部分對應(yīng)的內(nèi)存。

但是,由于request很可能在主線程中在 destroy() 方法被調(diào)用了內(nèi)部map的 clear() 方法,導(dǎo)致無法獲取到header的值。

testVo是用戶創(chuàng)建的對象,無法事先被放到 destroy() 方法中被釋放,所以還能繼續(xù)保持原有的值。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/73802.html

相關(guān)文章

  • Spring Boot QuickStart (3) - Web & Restful

    摘要:不過可以切換到版本,兼容性未知。注解一旦添加了依賴會判斷這是一個應(yīng)用,并啟動一個內(nèi)嵌的容器默認是用于處理請求。注意中空字符串與的區(qū)別。 環(huán)境:Spring Boot 1.5.4 基于 Spring Boot 可以快速創(chuàng)建一個Web & Restful 應(yīng)用,在開始應(yīng)用之前,至少要了解以下用法: 定義路由,定義 HTTP 方法 獲取Header、GET、POST、路徑等參數(shù) Cooki...

    chnmagnus 評論0 收藏0
  • 日志排查問題困難?分布式日志鏈路跟蹤來幫你

    摘要:當(dāng)前線程的子線程會繼承其父線程中的的內(nèi)容。若希望在線程池與主線程間傳遞,需配合和使用。 一、背景 開發(fā)排查系統(tǒng)問題用得最多的手段就是查看系統(tǒng)日志,在分布式環(huán)境中一般使用ELK來統(tǒng)一收集日志,但是在并發(fā)大時使用日志定位問題還是比較麻煩,由于大量的其他用戶/其他線程的日志也一起輸出穿行其中導(dǎo)致很難篩選出指定請求的全部相關(guān)日志,以及下游線程/服務(wù)對應(yīng)的日志。 ? 二、解決思路 每個請求都使...

    EasonTyler 評論0 收藏0
  • 使用servlet3.0異步特性改造spring-cloud-zuul

    摘要:不過在出來之后支持異步了,可以把業(yè)務(wù)操作放到獨立的線程池里面去,這樣可以盡快釋放線程,本身也支持異步了,本篇文章將帶你如何使用的異步特性來改造優(yōu)化其性能。 ? 我們知道spring-cloud-zuul是依賴springMVC來注冊路由的,而springMVC又是在建立在servlet之上的(這里微服務(wù)專家楊波老師寫過一篇文章講述其網(wǎng)絡(luò)模型,可以參考看看),在servlet3.0...

    HmyBmny 評論0 收藏0
  • 當(dāng)ThreadLocal碰上線程

    ThreadLocal可以讓線程擁有本地變量,在web環(huán)境中,為了方便代碼解耦,我們通常用它來保存上下文信息,然后用一個util類提供訪問入口,從controller層到service層可以很方便的獲取上下文。下面我們通過代碼來研究一下ThreadLocal。 新建一個ThreadContext類,用于保存線程上下文信息 public class ThreadContext { priva...

    instein 評論0 收藏0
  • 慕課網(wǎng)_《SpringBoot開發(fā)常用技術(shù)整合》學(xué)習(xí)總結(jié)

    摘要:時間年月日星期四說明本文部分內(nèi)容均來自慕課網(wǎng)。哈希表實現(xiàn)命令,將哈希表中的域的值設(shè)為實現(xiàn)命令,返回哈希表中給定域的值實現(xiàn)命令,刪除哈希表中的一個或多個指定域,不存在的域?qū)⒈缓雎浴崿F(xiàn)命令,返回哈希表中,所有的域和值。 時間:2018年04月19日星期四說明:本文部分內(nèi)容均來自慕課網(wǎng)。@慕課網(wǎng):https://www.imooc.com教學(xué)源碼:https://github.com/zc...

    chengtao1633 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<