<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Bean默認是單例的,高并發(fā)情況下,如何保證并發(fā)安全?

          共 5242字,需瀏覽 11分鐘

           ·

          2022-05-31 05:25

          點擊關注公眾號,Java干貨及時送達

          作者:DayDayUp丶

          來源:blog.csdn.net/songzehao/article/details/103365494



          Spring的bean默認都是單例的,某些情況下,單例是并發(fā)不安全的,以Controller舉例,問題根源在于,我們可能會在Controller中定義成員變量,如此一來,多個請求來臨,進入的都是同一個單例的Controller對象,并對此成員變量的值進行修改操作,因此會互相影響,無法達到并發(fā)安全(不同于線程隔離的概念,后面會解釋到)的效果

          1一、拋出問題

          首先來舉個例子,證明單例的并發(fā)不安全性:


          @Controller
          public?class?HomeController?{
          ????private?int?i;
          ????@GetMapping("testsingleton1")
          ????@ResponseBody
          ????public?int?test1()?{
          ????????return?++i;
          ????}
          }

          多次訪問此url,可以看到每次的結果都是自增的,所以這樣的代碼顯然是并發(fā)不安全的。

          2二、解決方案

          因此,我們?yōu)榱俗専o狀態(tài)的海量Http請求之間不受影響,我們可以采取以下幾種措施:

          2.1 單例變原型

          對web項目,可以Controller類上加注解@Scope("prototype")@Scope("request"),對非web項目,在Component類上添加注解@Scope("prototype")

          優(yōu)點:實現(xiàn)簡單;

          缺點:很大程度上增大了bean創(chuàng)建實例化銷毀的服務器資源開銷。

          2.2 線程隔離類ThreadLocal

          有人想到了線程隔離類ThreadLocal,我們嘗試將成員變量包裝為ThreadLocal,以試圖達到并發(fā)安全,同時打印出Http請求的線程名,修改代碼如下:


          @Controller
          public?class?HomeController?{
          ????private?ThreadLocal?i?=?new?ThreadLocal<>();
          ????@GetMapping("testsingleton1")
          ????@ResponseBody
          ????public?int?test1()?{
          ????????if?(i.get()?==?null)?{
          ????????????i.set(0);
          ????????}
          ????????i.set(i.get().intValue()?+?1);
          ????????log.info("{}?->?{}",?Thread.currentThread().getName(),?i.get());
          ????????return?i.get().intValue();
          ????}
          }

          多次訪問此url測試一把,打印日志如下:


          [INFO ] 2021-12-03 11:49:08,226 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-1 -> 1
          [INFO ] 2021-12-03 11:49:16,457 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-2 -> 1
          [INFO ] 2021-12-03 11:49:17,858 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-3 -> 1
          [INFO ] 2021-12-03 11:49:18,461 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-4 -> 1
          [INFO ] 2021-12-03 11:49:18,974 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-5 -> 1
          [INFO ] 2021-12-03 11:49:19,696 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-6 -> 1
          [INFO ] 2021-12-03 11:49:22,138 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-7 -> 1
          [INFO ] 2021-12-03 11:49:22,869 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-9 -> 1
          [INFO ] 2021-12-03 11:49:23,617 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-8 -> 1
          [INFO ] 2021-12-03 11:49:24,569 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-10 -> 1
          [INFO ] 2021-12-03 11:49:25,218 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-1 -> 2
          [INFO ] 2021-12-03 11:49:25,740 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-2 -> 2
          [INFO ] 2021-12-03 11:49:43,308 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-3 -> 2
          [INFO ] 2021-12-03 11:49:44,420 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-4 -> 2
          [INFO ] 2021-12-03 11:49:45,271 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-5 -> 2
          [INFO ] 2021-12-03 11:49:45,808 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-6 -> 2
          [INFO ] 2021-12-03 11:49:46,272 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-7 -> 2
          [INFO ] 2021-12-03 11:49:46,489 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-9 -> 2
          [INFO ] 2021-12-03 11:49:46,660 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-8 -> 2
          [INFO ] 2021-12-03 11:49:46,820 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-10 -> 2
          [INFO ] 2021-12-03 11:49:46,990 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-1 -> 3
          [INFO ] 2021-12-03 11:49:47,163 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-2 -> 3
          ......

          從日志分析出,二十多次的連續(xù)請求得到的結果有1有2有3等等,而我們期望不管我并發(fā)請求有多少,每次的結果都是1;同時可以發(fā)現(xiàn)web服務器默認的請求線程池大小為10,這10個核心線程可以被之后不同的Http請求復用,所以這也是為什么相同線程名的結果不會重復的原因。

          總結:ThreadLocal的方式可以達到線程隔離,但還是無法達到并發(fā)安全。

          2.3 盡量避免使用成員變量

          有人說,單例bean的成員變量這么麻煩,能不用成員變量就盡量避免這么用,在業(yè)務允許的條件下,將成員變量替換為RequestMapping方法中的局部變量,多省事。這種方式自然是最恰當?shù)模救艘彩亲钔扑]。代碼修改如下:


          @Controller
          public?class?HomeController?{
          ????@GetMapping("testsingleton1")
          ????@ResponseBody
          ????public?int?test1()?{
          ?????????int?i?=?0;
          ?????????//?TODO?biz?code
          ?????????return?++i;
          ????}
          }

          但當很少的某種情況下,必須使用成員變量呢,我們該怎么處理?

          2.4 使用并發(fā)安全的類

          Java作為功能性超強的編程語言,API豐富,如果非要在單例bean中使用成員變量,可以考慮使用并發(fā)安全的容器,如ConcurrentHashMap、ConcurrentHashSet等等等等,將我們的成員變量(一般可以是當前運行中的任務列表等這類變量)包裝到這些并發(fā)安全的容器中進行管理即可。

          2.5 分布式或微服務的并發(fā)安全

          如果還要進一步考慮到微服務或分布式服務的影響,方式4便不足以處理了,所以可以借助于可以共享某些信息的分布式緩存中間件如Redis等,這樣即可保證同一種服務的不同服務實例都擁有同一份共享信息(如當前運行中的任務列表等這類變量)。另外,歡迎關注公眾號后端面試那些事,回復:簡歷,即可免費獲取優(yōu)質簡歷模板。

          3三、補充說明

          spring bean作用域有以下5個:

          • singleton:單例模式,當spring創(chuàng)建applicationContext容器的時候,spring會欲初始化所有的該作用域實例,加上lazy-init就可以避免預處理;
          • prototype:原型模式,每次通過getBean獲取該bean就會新產生一個實例,創(chuàng)建后spring將不再對其管理;

          (下面是在web項目下才用到的)

          • request:搞web的大家都應該明白request的域了吧,就是每次請求都新產生一個實例,和prototype不同就是創(chuàng)建后,接下來的管理,spring依然在監(jiān)聽;
          • session:每次會話,同上;
          • global session:全局的web域,類似于servlet中的application。

          ? ? ?

          1、我在產品上線前不小心刪除了7 TB的視頻

          2、程序員最硬大佬,你絕對想不到!!!

          3、IntelliJ IDEA快捷鍵大全 + 動圖演示

          4、打不過就加入?微軟強推“親兒子”上位,還是中國特供版

          5、活久見!NVIDIA正式開源其Linux GPU內核模塊

          點分享

          點收藏

          點點贊

          點在看

          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  骚逼熟女 | 曰韩欧美一级 | 日本成人麻豆三级 | 久久青青草在线视频 | 色婷婷18|