Spring中單例Bean的執行緒安全問題採坑

2020-10-13 12:00:46

 
    Spring整合的專案中,通過註解方式注入Bean的使用無處不在。在一次專案開發中,由voliate關鍵字思考到的Spring中單例Bean的執行緒安全問題,在此記錄採坑總結。

    Spring的單例Bean(@Autowired等註解的Bean)預設是單例的,Spring並沒有對Bean有執行緒安全的控制策略,並行安全問題由開發決定。然而,執行緒安全與否取決於是否有共用的資源競爭關係存在,Spring中單例Bean是否是執行緒安全取決於Bean是有狀態的Bean還是無狀態的Bean。

 

何為有狀態Bean跟無狀態Bean?

  • 有狀態的Bean:有資料儲存功能,eg:Bean中有User物件用於儲存使用者資訊;
  • 無狀態的Bean:無資料儲存功能

 
下面是測試的Demo:

(1)MainUser.java:

@Data
@Builder
public class MainUser {

    private String id;

    private String sex;

    private String userName;

}

(2)SysMainController .java

@RequestMapping("/main")
@Controller
public class SysMainController {

    @Autowired
    private SysMainService mainService;

    @RequestMapping(value = "/test")
    public void mainMethod() {

        // thread-1
        new Thread(() -> {
            MainUser user = MainUser.builder()
                    .id("1")
                    .sex("男")
                    .userName("張三")
                    .build();
            mainService.testBean(user);
        }).start();

        // thread-2
        new Thread(() -> {
            MainUser user = MainUser.builder()
                    .id("2")
                    .sex("女")
                    .userName("小芳")
                    .build();
            mainService.testBean(user);
        }).start();

    }

}

(3)SysMainServiceImpl.java

@Service
public class SysMainServiceImpl implements SysMainService {

	/** 基本型別變數亦可 */
    private MainUser user; // 非定義為static的變數

    @Override
    public void testBean(MainUser user) {
        this.user = user;
        print();
    }

    public void print() {
        try {
            Thread.sleep(2000);
            System.out.println("ThreadName: " + Thread.currentThread().getName() + ", user: " + user);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

測試多次的結果:

ThreadName: Thread-57, user: MainUser(id=2, sex=, userName=小芳)
ThreadName: Thread-58, user: MainUser(id=2, sex=, userName=小芳)

 
結論:

  • 兩個執行緒進入print()方法都會建立自己的私有VM,但是user物件是共用的,這點容易在開發中被忽略;
  • 故在Spring的單例模式的Bean中不應該在全域性層面上定義有資料儲存功能的物件 / 基本型別變數 / static變數,會產生執行緒安全問題;若要這麼做,可以通過宣告Bean的 Scope=‘prototype’ 或者使用本地變數 ThreadLocal 進行定義

 
以上為筆者遇到的問題進行的思考實驗,若有不正確的地方歡迎指出~~~