<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>

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

          共 7294字,需瀏覽 15分鐘

           ·

          2021-07-06 12:02

          往期熱門文章:
          1、知乎高贊:拼多多和國(guó)家電網(wǎng),選哪個(gè)?
          2、Windows 11 全網(wǎng)曝光,果然一股子蘋果味...
          3、小團(tuán)隊(duì)真的適合引入Spring Cloud微服務(wù)嗎?
          4、卷不動(dòng)了?300 秒快速了解 Java 9 - 16 新特性,助你脫離內(nèi)卷
          5、Java 程序員常犯的 10 個(gè) SQL 錯(cuò)誤!

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


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

          一、拋出問題

          首先來舉個(gè)例子,證明單例的并發(fā)不安全性:
          @Controller
          public class HomeController {
              private int i;
              @GetMapping("testsingleton1")
              @ResponseBody
              public int test1() {
                  return ++i;
              }
          }
          多次訪問此url,可以看到每次的結(jié)果都是自增的,所以這樣的代碼顯然是并發(fā)不安全的。

          二、解決方案

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

          2.1 單例變?cè)?/span>

          對(duì)web項(xiàng)目,可以Controller類上加注解@Scope("prototype")@Scope("request"),對(duì)非web項(xiàng)目,在Component類上添加注解@Scope("prototype")
          優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單;
          缺點(diǎn):很大程度上增大了bean創(chuàng)建實(shí)例化銷毀的服務(wù)器資源開銷。

          2.2 線程隔離類ThreadLocal

          有人想到了線程隔離類ThreadLocal,我們嘗試將成員變量包裝為ThreadLocal,以試圖達(dá)到并發(fā)安全,同時(shí)打印出Http請(qǐng)求的線程名,修改代碼如下:
          @Controller
          public class HomeController {
              private ThreadLocal<Integer> 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測(cè)試一把,打印日志如下:
          [INFO ] 2019-12-03 11:49:08,226 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-1 -> 1
          [INFO ] 2019-12-03 11:49:16,457 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-2 -> 1
          [INFO ] 2019-12-03 11:49:17,858 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-3 -> 1
          [INFO ] 2019-12-03 11:49:18,461 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-4 -> 1
          [INFO ] 2019-12-03 11:49:18,974 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-5 -> 1
          [INFO ] 2019-12-03 11:49:19,696 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-6 -> 1
          [INFO ] 2019-12-03 11:49:22,138 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-7 -> 1
          [INFO ] 2019-12-03 11:49:22,869 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-9 -> 1
          [INFO ] 2019-12-03 11:49:23,617 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-8 -> 1
          [INFO ] 2019-12-03 11:49:24,569 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-10 -> 1
          [INFO ] 2019-12-03 11:49:25,218 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-1 -> 2
          [INFO ] 2019-12-03 11:49:25,740 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-2 -> 2
          [INFO ] 2019-12-03 11:49:43,308 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-3 -> 2
          [INFO ] 2019-12-03 11:49:44,420 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-4 -> 2
          [INFO ] 2019-12-03 11:49:45,271 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-5 -> 2
          [INFO ] 2019-12-03 11:49:45,808 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-6 -> 2
          [INFO ] 2019-12-03 11:49:46,272 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-7 -> 2
          [INFO ] 2019-12-03 11:49:46,489 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-9 -> 2
          [INFO ] 2019-12-03 11:49:46,660 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-8 -> 2
          [INFO ] 2019-12-03 11:49:46,820 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-10 -> 2
          [INFO ] 2019-12-03 11:49:46,990 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-1 -> 3
          [INFO ] 2019-12-03 11:49:47,163 com.cjia.ds.controller.HomeController.test1(HomeController.java:50)
          http-nio-8080-exec-2 -> 3
          ......
          從日志分析出,二十多次的連續(xù)請(qǐng)求得到的結(jié)果有1有2有3等等,而我們期望不管我并發(fā)請(qǐng)求有多少,每次的結(jié)果都是1;同時(shí)可以發(fā)現(xiàn)web服務(wù)器默認(rèn)的請(qǐng)求線程池大小為10,這10個(gè)核心線程可以被之后不同的Http請(qǐng)求復(fù)用,所以這也是為什么相同線程名的結(jié)果不會(huì)重復(fù)的原因。
          總結(jié):ThreadLocal的方式可以達(dá)到線程隔離,但還是無法達(dá)到并發(fā)安全。

          2.3 盡量避免使用成員變量

          有人說,單例bean的成員變量這么麻煩,能不用成員變量就盡量避免這么用,在業(yè)務(wù)允許的條件下,將成員變量替換為RequestMapping方法中的局部變量,多省事。這種方式自然是最恰當(dāng)?shù)模救艘彩亲钔扑]。代碼修改如下:
          @Controller
          public class HomeController {
              @GetMapping("testsingleton1")
              @ResponseBody
              public int test1() {
                   int i = 0;
                   // TODO biz code
                   return ++i;
              }
          }
          但當(dāng)很少的某種情況下,必須使用成員變量呢,我們?cè)撛趺刺幚恚?/span>

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

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

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

          如果還要進(jìn)一步考慮到微服務(wù)或分布式服務(wù)的影響,方式4便不足以處理了,所以可以借助于可以共享某些信息的分布式緩存中間件如Redis等,這樣即可保證同一種服務(wù)的不同服務(wù)實(shí)例都擁有同一份共享信息(如當(dāng)前運(yùn)行中的任務(wù)列表等這類變量)。另外,歡迎關(guān)注公眾號(hào)Java筆記蝦,后臺(tái)回復(fù)“后端面試”,送你一份面試題寶典!

          三、補(bǔ)充說明

          spring bean作用域有以下5個(gè):
          • singleton:?jiǎn)卫J剑?dāng)spring創(chuàng)建applicationContext容器的時(shí)候,spring會(huì)欲初始化所有的該作用域?qū)嵗由蟣azy-init就可以避免預(yù)處理;
          • prototype:原型模式,每次通過getBean獲取該bean就會(huì)新產(chǎn)生一個(gè)實(shí)例,創(chuàng)建后spring將不再對(duì)其管理;
          (下面是在web項(xiàng)目下才用到的)
          • request:搞web的大家都應(yīng)該明白request的域了吧,就是每次請(qǐng)求都新產(chǎn)生一個(gè)實(shí)例,和prototype不同就是創(chuàng)建后,接下來的管理,spring依然在監(jiān)聽;
          • session:每次會(huì)話,同上;
          • global session:全局的web域,類似于servlet中的application。


          最近熱文閱讀:

          1、知乎高贊:拼多多和國(guó)家電網(wǎng),選哪個(gè)?
          2、Windows 11 全網(wǎng)曝光,果然一股子蘋果味...
          3、小團(tuán)隊(duì)真的適合引入Spring Cloud微服務(wù)嗎?
          4、卷不動(dòng)了?300 秒快速了解 Java 9 - 16 新特性,助你脫離內(nèi)卷
          5、Java 程序員常犯的 10 個(gè) SQL 錯(cuò)誤!
          6、SpringBoot+webSocket實(shí)現(xiàn)掃碼登錄功能
          7、短 URL 服務(wù)的設(shè)計(jì)以及實(shí)現(xiàn)
          8、為什么阿里巴巴禁止使用存儲(chǔ)過程?
          9、面試官:說一下JDK/Dubbo/Spring 三種 SPI 機(jī)制,誰更好?
          10、在外包干了三年,我廢了..… 不吹不黑!
          關(guān)注公眾號(hào),你想要的Java都在這里

          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  天天爱天天色 | 久久夜色精品国产噜噜v6 | 区美一级AA免费观看 | 亚洲国产正在播放 | www.五月天 |