摘要:在中設(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ā)送
Main thread: 1.0.1Child 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ā)送
Main thread: 1.0.1Child 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.1Child 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.1Child 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
摘要:不過可以切換到版本,兼容性未知。注解一旦添加了依賴會判斷這是一個應(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...
摘要:當(dāng)前線程的子線程會繼承其父線程中的的內(nèi)容。若希望在線程池與主線程間傳遞,需配合和使用。 一、背景 開發(fā)排查系統(tǒng)問題用得最多的手段就是查看系統(tǒng)日志,在分布式環(huán)境中一般使用ELK來統(tǒng)一收集日志,但是在并發(fā)大時使用日志定位問題還是比較麻煩,由于大量的其他用戶/其他線程的日志也一起輸出穿行其中導(dǎo)致很難篩選出指定請求的全部相關(guān)日志,以及下游線程/服務(wù)對應(yīng)的日志。 ? 二、解決思路 每個請求都使...
摘要:不過在出來之后支持異步了,可以把業(yè)務(wù)操作放到獨立的線程池里面去,這樣可以盡快釋放線程,本身也支持異步了,本篇文章將帶你如何使用的異步特性來改造優(yōu)化其性能。 ? 我們知道spring-cloud-zuul是依賴springMVC來注冊路由的,而springMVC又是在建立在servlet之上的(這里微服務(wù)專家楊波老師寫過一篇文章講述其網(wǎng)絡(luò)模型,可以參考看看),在servlet3.0...
ThreadLocal可以讓線程擁有本地變量,在web環(huán)境中,為了方便代碼解耦,我們通常用它來保存上下文信息,然后用一個util類提供訪問入口,從controller層到service層可以很方便的獲取上下文。下面我們通過代碼來研究一下ThreadLocal。 新建一個ThreadContext類,用于保存線程上下文信息 public class ThreadContext { priva...
摘要:時間年月日星期四說明本文部分內(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...
閱讀 3520·2021-11-25 09:43
閱讀 1279·2021-09-08 09:45
閱讀 2651·2021-09-07 09:59
閱讀 1515·2021-08-09 13:45
閱讀 3362·2019-08-30 15:54
閱讀 702·2019-08-29 18:35
閱讀 522·2019-08-29 17:18
閱讀 1004·2019-08-29 14:10