<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 是線程安全的嗎?

          共 8331字,需瀏覽 17分鐘

           ·

          2021-03-14 17:58


          作者:myseries

          cnblogs.com/myseries/p/11729800.html

          結(jié)論:不是線程安全的

          Spring容器中的Bean是否線程安全,容器本身并沒有提供Bean的線程安全策略,因此可以說Spring容器中的Bean本身不具備線程安全的特性,但是具體還是要結(jié)合具體scope的Bean去研究。

          Spring 的 bean 作用域(scope)類型

          • singleton:單例,默認(rèn)作用域。
          • prototype:原型,每次創(chuàng)建一個新對象。
          • request:請求,每次Http請求創(chuàng)建一個新對象,適用于WebApplicationContext環(huán)境下。
          • session:會話,同一個會話共享一個實例,不同會話使用不用的實例。
          • global-session:全局會話,所有會話共享一個實例。

          線程安全這個問題,要從單例與原型Bean分別進(jìn)行說明。

          原型Bean

          對于原型Bean,每次創(chuàng)建一個新對象,也就是線程之間并不存在Bean共享,自然是不會有線程安全的問題。

          單例Bean

          對于單例Bean,所有線程都共享一個單例實例Bean,因此是存在資源的競爭。

          如果單例Bean,是一個無狀態(tài)Bean,也就是線程中的操作不會對Bean的成員執(zhí)行查詢以外的操作,那么這個單例Bean是線程安全的。比如Spring mvc 的 Controller、Service、Dao等,這些Bean大多是無狀態(tài)的,只關(guān)注于方法本身。

          spring單例,為什么controller、service和dao確能保證線程安全?

          Spring中的Bean默認(rèn)是單例模式的,框架并沒有對bean進(jìn)行多線程的封裝處理。

          實際上大部分時間Bean是無狀態(tài)的(比如Dao) 所以說在某種程度上來說Bean其實是安全的。

          但是如果Bean是有狀態(tài)的 那就需要開發(fā)人員自己來進(jìn)行線程安全的保證,最簡單的辦法就是改變bean的作用域 把 "singleton"改為’‘protopyte’ 這樣每次請求Bean就相當(dāng)于是 new Bean() 這樣就可以保證線程的安全了。

          • 有狀態(tài)就是有數(shù)據(jù)存儲功能
          • 無狀態(tài)就是不會保存數(shù)據(jù)    controller、service和dao層本身并不是線程安全的,只是如果只是調(diào)用里面的方法,而且多線程調(diào)用一個實例的方法,會在內(nèi)存中復(fù)制變量,這是自己的線程的工作內(nèi)存,是安全的。

          想理解原理可以看看《深入理解JVM虛擬機(jī)》,2.2.2節(jié):

          Java虛擬機(jī)棧是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。

          《Java并發(fā)編程實戰(zhàn)》第3.2.2節(jié):

          局部變量的固有屬性之一就是封閉在執(zhí)行線程中。它們位于執(zhí)行線程的棧中,其他線程無法訪問這個棧。

          所以其實任何無狀態(tài)單例都是線程安全的。

          Spring的根本就是通過大量這種單例構(gòu)建起系統(tǒng),以事務(wù)腳本的方式提供服務(wù)。搜索我是程序汪公眾號,回復(fù)“2020”,送你一份Java面試題寶典.pdf

          首先問@Controller @Service是不是線程安全的?

          答:默認(rèn)配置下不是的。為啥呢?因為默認(rèn)情況下@Controller沒有加上@Scope,沒有加@Scope就是默認(rèn)值singleton,單例的。意思就是系統(tǒng)只會初始化一次Controller容器,所以每次請求的都是同一個Controller容器,當(dāng)然是非線程安全的。舉個栗子:

          @RestController
          public class TestController {

              private int var = 0;
              
              @GetMapping(value = "/test_var")
              public String test() {
                  System.out.println("普通變量var:" + (++var));
                  return "普通變量var:" + var ;
              }
          }

          在postman里面發(fā)三次請求,結(jié)果如下:

          普通變量var:1
          普通變量var:2
          普通變量var:3

          說明他不是線程安全的。怎么辦呢?可以給他加上上面說的@Scope注解,如下:

          @RestController
          @Scope(value = "prototype"// 加上@Scope注解,他有2個取值:單例-singleton 多實例-prototype
          public class TestController {

              private int var = 0;
              
              @GetMapping(value = "/test_var")
              public String test() {
                  System.out.println("普通變量var:" + (++var));
                  return "普通變量var:" + var ;
              }
          }

          這樣一來,每個請求都單獨創(chuàng)建一個Controller容器,所以各個請求之間是線程安全的,三次請求結(jié)果:

          普通變量var:1
          普通變量var:1
          普通變量var:1

          加了@Scope注解多的實例prototype是不是一定就是線程安全的呢?

          @RestController
          @Scope(value = "prototype"// 加上@Scope注解,他有2個取值:單例-singleton 多實例-prototype
          public class TestController {
              private int var = 0;
              private static int staticVar = 0;

              @GetMapping(value = "/test_var")
              public String test() {
                  System.out.println("普通變量var:" + (++var)+ "---靜態(tài)變量staticVar:" + (++staticVar));
                  return "普通變量var:" + var + "靜態(tài)變量staticVar:" + staticVar;
              }
          }

          看三次請求結(jié)果:

          普通變量var:1---靜態(tài)變量staticVar:1
          普通變量var:1---靜態(tài)變量staticVar:2
          普通變量var:1---靜態(tài)變量staticVar:3

          雖然每次都是單獨創(chuàng)建一個Controller但是扛不住他變量本身是static的呀,所以說呢,即便是加上@Scope注解也不一定能保證Controller 100%的線程安全。所以是否線程安全在于怎樣去定義變量以及Controller的配置。


          所以來個全乎一點的實驗,代碼如下:

          @RestController
          @Scope(value = "singleton"// prototype singleton
          public class TestController {

              private int var = 0// 定義一個普通變量

              private static int staticVar = 0// 定義一個靜態(tài)變量

              @Value("${test-int}")
              private int testInt; // 從配置文件中讀取變量

              ThreadLocal<Integer> tl = new ThreadLocal<>(); // 用ThreadLocal來封裝變量

              @Autowired
              private User user; // 注入一個對象來封裝變量

              @GetMapping(value = "/test_var")
              public String test() {
                  tl.set(1);
                  System.out.println("先取一下user對象中的值:"+user.getAge()+"===再取一下hashCode:"+user.hashCode());
                  user.setAge(1);
                  System.out.println("普通變量var:" + (++var) + "===靜態(tài)變量staticVar:" + (++staticVar) + "===配置變量testInt:" + (++testInt)
                          + "===ThreadLocal變量tl:" + tl.get()+"===注入變量user:" + user.getAge());
                  return "普通變量var:" + var + ",靜態(tài)變量staticVar:" + staticVar + ",配置讀取變量testInt:" + testInt + ",ThreadLocal變量tl:"
                          + tl.get() + "注入變量user:" + user.getAge();
              }
          }

          補(bǔ)充Controller以外的代碼:

          config里面自己定義的Bean:User

          @Configuration
          public class MyConfig {
              @Bean
              public User user(){
                  return new User();
              }
          }

          我暫時能想到的定義變量的方法就這么多了,三次http請求結(jié)果如下:

          先取一下user對象中的值:0===再取一下hashCode:241165852
          普通變量var:1===靜態(tài)變量staticVar:1===配置變量testInt:1===ThreadLocal變量tl:1===注入變量user:1
          先取一下user對象中的值:1===再取一下hashCode:241165852
          普通變量var:2===靜態(tài)變量staticVar:2===配置變量testInt:2===ThreadLocal變量tl:1===注入變量user:1
          先取一下user對象中的值:1===再取一下hashCode:241165852
          普通變量var:3===靜態(tài)變量staticVar:3===配置變量testInt:3===ThreadLocal變量tl:1===注入變量user:1

          可以看到,在單例模式下Controller中只有用ThreadLocal封裝的變量是線程安全的。為什么這樣說呢?我們可以看到3次請求結(jié)果里面只有ThreadLocal變量值每次都是從0+1=1的,其他的幾個都是累加的,而user對象呢,默認(rèn)值是0,第二交取值的時候就已經(jīng)是1了,關(guān)鍵他的hashCode是一樣的,說明每次請求調(diào)用的都是同一個user對象。

          下面將TestController 上的@Scope注解的屬性改一下改成多實例的:@Scope(value = "prototype"),其他都不變,再次請求,結(jié)果如下:

          先取一下user對象中的值:0===再取一下hashCode:853315860
          普通變量var:1===靜態(tài)變量staticVar:1===配置變量testInt:1===ThreadLocal變量tl:1===注入變量user:1
          先取一下user對象中的值:1===再取一下hashCode:853315860
          普通變量var:1===靜態(tài)變量staticVar:2===配置變量testInt:1===ThreadLocal變量tl:1===注入變量user:1
          先取一下user對象中的值:1===再取一下hashCode:853315860
          普通變量var:1===靜態(tài)變量staticVar:3===配置變量testInt:1===ThreadLocal變量tl:1===注入變量user:1

          分析這個結(jié)果發(fā)現(xiàn),多實例模式下普通變量,取配置的變量還有ThreadLocal變量都是線程安全的,而靜態(tài)變量和user(看他的hashCode都是一樣的)對象中的變量都是非線程安全的。

          也就是說盡管TestController 是每次請求的時候都初始化了一個對象,但是靜態(tài)變量始終是只有一份的,而且這個注入的user對象也是只有一份的。靜態(tài)變量只有一份這是當(dāng)然的咯,那么有沒有辦法讓user對象可以每次都new一個新的呢?當(dāng)然可以:

          public class MyConfig {
              @Bean
              @Scope(value = "prototype")
              public User user(){
                  return new User();
              }    
          }

          在config里面給這個注入的Bean加上一個相同的注解@Scope(value = "prototype")就可以了,再來請求一下看看:

          先取一下user對象中的值:0===再取一下hashCode:1612967699
          普通變量var:1===靜態(tài)變量staticVar:1===配置變量testInt:1===ThreadLocal變量tl:1===注入變量user:1
          先取一下user對象中的值:0===再取一下hashCode:985418837
          普通變量var:1===靜態(tài)變量staticVar:2===配置變量testInt:1===ThreadLocal變量tl:1===注入變量user:1
          先取一下user對象中的值:0===再取一下hashCode:1958952789
          普通變量var:1===靜態(tài)變量staticVar:3===配置變量testInt:1===ThreadLocal變量tl:1===注入變量user:1

          可以看到每次請求的user對象的hashCode都不是一樣的,每次賦值前取user中的變量值也都是默認(rèn)值0。

          下面總結(jié)一下:

          1、在@Controller/@Service等容器中,默認(rèn)情況下,scope值是單例-singleton的,也是線程不安全的。

          2、盡量不要在@Controller/@Service等容器中定義靜態(tài)變量,不論是單例(singleton)還是多實例(prototype)他都是線程不安全的。

          3、默認(rèn)注入的Bean對象,在不設(shè)置scope的時候他也是線程不安全的。

          4、一定要定義變量的話,用ThreadLocal來封裝,這個是線程安全的

          END

          往期資源  需要請自取

          真香警告!Alibaba珍藏版mybatis手寫文檔,刷起來

          臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載

          字節(jié)跳動總結(jié)的設(shè)計模式 PDF 火了,完整版開放下載!

          堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進(jìn)階

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          瀏覽 23
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  午夜精品少妇 | 最新欧美日韩中文 | 在线免费看黄网站 | 豆花视频网站国产 | 国产手机自拍视频 |