欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

對Spring中bean線程安全的討論

 更新時間:2025年06月29日 13:51:21   作者:找不到、了  
這篇文章主要介紹了對Spring中bean線程安全的討論,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

Spring容器中的Bean是否線程安全,容器本身并沒有提供Bean的線程安全策略,因此Spring容器中的Bean本身不具備線程安全的特性,但是具體要結(jié)合具體的scope、靜態(tài)變量、常量、成員變量等多種屬性去研究。   

1、Bean狀態(tài)介紹

1.1、有狀態(tài)對象

有實(shí)例變量的對象,即每個用戶最初都會得到一個初始的bean,可以保存數(shù)據(jù),是非線程安全的。

每個用戶有自己特有的一個實(shí)例,在用戶的生存期內(nèi),bean保持了用戶的信息,即“有狀態(tài)”;一旦用戶滅亡(調(diào)用結(jié)束或?qū)嵗Y(jié)束),bean的生命期也告結(jié)束。

代碼示例:

@Service
public class Counter {
    private int count = 0;  // 有狀態(tài):保存實(shí)例變量

    public void increment() {
        count++;  // 非原子操作,線程不安全
    }

    public int getCount() {
        return count;
    }
}
  • 多個線程調(diào)用 increment() 時,count++操作可能因指令重排或并發(fā)寫入導(dǎo)致數(shù)據(jù)不一致。

1.2、無狀態(tài)對象

沒有實(shí)例變量的對象,不能保存數(shù)據(jù),是不變類,是線程安全的。

  • bean一旦實(shí)例化就被加進(jìn)會話池中,各個用戶都可以共用。即使用戶已經(jīng)消亡,bean 的生命期也不一定結(jié)束,它可能依然存在于會話池中,供其他用戶調(diào)用。
  • 由于沒有特定的用戶,那么也就不能保持某一用戶的狀態(tài),所以叫無狀態(tài)bean。但無狀態(tài)會話bean 并非沒有狀態(tài),
  • 如果有自己的屬性(變量),那么這些變量就會受到所有調(diào)用它的用戶的影響。

代碼示例如下:

@Service
public class Calculator {
    // 無狀態(tài):不保存任何實(shí)例變量
    public int add(int a, int b) {
        return a + b;
    }
}
  • 多個線程調(diào)用add(1,2)時,結(jié)果不會互相影響。

兩者的區(qū)別和聯(lián)系:

2、Bean作用域

bean的生命周期如下所示:

實(shí)例化--->設(shè)置屬性--->初始化--->銷毀

Spring 的 bean 作用域(scope)類型:

1、singleton:單例,默認(rèn)作用域。

  • 優(yōu)點(diǎn): 節(jié)省內(nèi)存,因?yàn)橹淮嬖谝粋€實(shí)例。
  • 缺點(diǎn): 由于多個線程可能共享同一個實(shí)例,需要格外注意線程安全(非線程安全的狀態(tài)字段可能導(dǎo)致問題)

2、prototype:原型,每次創(chuàng)建一個新對象。

3、request:請求,每次Http請求創(chuàng)建一個新對象,適用于WebApplicationContext環(huán)境下。

4、session:會話,同一個會話共享一個實(shí)例,不同會話使用不用的實(shí)例。

5、global-session:全局會話,所有會話共享一個實(shí)例。

3、線程安全:

從單例與原型Bean分別進(jìn)行說明。

3.1、bean的分類

1、原型Bean

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

2、單例Bean

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

3.2、bean的安全

1、@Controller相關(guān)

可以這樣理解:

如果單例Bean,是一個無狀態(tài)Bean,在線程中的操作不會對Bean的成員執(zhí)行查詢以外的操作,那么這個單例Bean是線程安全的。

比如Spring mvc 的 Controller、Service、Dao等,這些Bean大多是無狀態(tài)的,默認(rèn)情況下@Controller沒有加上@Scope,默認(rèn)Scope就是默認(rèn)值singleton,單例的 ,系統(tǒng)只會初始化一次 Controller 容器,只關(guān)注于方法本身。

但是,如果每次請求的都是同一個 Controller 容器里面的非線程安全的字段,那么就不是線程安全的。

代碼示例:

@RestController
public class TestController {
    //非線程安全的字段
    private int var = 0;
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通變量var:" + (++var));
        return "普通變量var:" + var ;
    }
}

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

修改了作用于改為:prototype

每個請求都單獨(dú)創(chuàng)建一個 Controller 容器,所以各個請求之間是線程安全的。

@RestController
@Scope(value = "prototype") // 加上@Scope注解,有2個取值:單例-singleton 多實(shí)例-prototype
public class TestController {
    private int var = 0;
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通變量var:" + (++var));
        return "普通變量var:" + var ;
    }
}
輸出:
普通變量var:1
普通變量var:1
普通變量var:1

總結(jié)

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

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

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

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

2、@prototype注解

@Scope 注解的 prototype 實(shí)例一定就是線程安全的嗎?

答案是否定的。上面已經(jīng)解釋過了,需要根據(jù)多方位去考量。

@RestController
@Scope(value = "prototype") // 加上@Scope注解,有2個取值:單例-singleton 多實(shí)例-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;
    }
}

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

總結(jié):線程安全在于怎樣去定義變量以及 Controller 的配置。

示例:

config里面自己定義的Bean: User

@Configuration
public class MyConfig {
    @Bean
    public User user(){
        return new User();
    }
}
@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();
    }
}

輸出:

先取一下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 封裝的變量是線程安全的??梢钥吹?次請求結(jié)果里面只有 ThreadLocal 變量值每次都是從 0+1=1 的,其他的幾個都是累加的,而 user 對象呢,默認(rèn)值是0,第二交取值的時候就已經(jīng)是1了,關(guān)鍵它的 hashCode 是一樣的,說明每次請求調(diào)用的都是同一個 user 對象。

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

public class MyConfig {
    @Bean
    @Scope(value = "prototype")
    public User user(){
        return new User();
    }    
}
@RestController
@Scope(value = "prototype") // 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();
    }
}

先取一下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

3、靜態(tài)變量

靜態(tài)變量的生命周期由 JVM 管理,與 Spring 無關(guān)。所有實(shí)例(單例或原型)共享同一個靜態(tài)變量。

@Component
public class MyService {
    private static int count = 0; // 靜態(tài)變量

    public void increment() {
        count++; // 多線程環(huán)境下可能出問題
    }
}

在 Spring 中,無論是單例(Singleton)作用域還是原型(Prototype)作用域的 Bean,只要在類中定義了靜態(tài)變量(static 變量),都可能存在線程安全問題。

總結(jié):多實(shí)例模式下普通變量,取配置的變量還有 ThreadLocal 變量都是線程安全的,而靜態(tài)變量和 user 對象中的變量都是非線程安全的。

4、ThreadLocal

4.1、概念

ThreadLocal 類提供了線程局部變量,每個線程可以將一個值存在 ThreadLocal 對象中,其他線程無法訪問這些值。每個線程都有自己獨(dú)立的變量副本。

ThreadLocal 的初始值可通過 withInitial() 方法設(shè)置:

private static final ThreadLocal<String> requestId = 
    ThreadLocal.withInitial(() -> "default-id");

簡單的內(nèi)存模型:

+-----------------+          +------------------+
|    Thread A     |          |    Thread B      |
+-----------------+          +------------------+
| ThreadLocal     |          | ThreadLocal      |
| - value: 123    |          | - value: 456     |
+-----------------+          +------------------+

Thread A and Thread B can have different values in the same ThreadLocal.

不同線程直接保存了不同的值。

4.2、優(yōu)點(diǎn)

若單例 Bean 需要保存線程私有的狀態(tài)(如用戶請求上下文),多線程場景下,多個線程對這個單例Bean的成員變量并不存在資源的競爭,因?yàn)門hreadLocal為每個線程保存線程私有的數(shù)據(jù)。這是一種以空間換時間的方式。

4.3、原理

如下圖所示:

調(diào)用 ThreadLocal.set(value)方法時,它會將這個值與當(dāng)前線程關(guān)聯(lián),而該值被存儲在當(dāng)前線程的一個內(nèi)部數(shù)據(jù)結(jié)構(gòu)中。通過 ThreadLocal.get()方法,可以獲取當(dāng)前線程所關(guān)聯(lián)的值。

  • 核心機(jī)制:每個線程內(nèi)部維護(hù)一個 ThreadLocalMap(類似鍵值對存儲,以 ThreadLocal 對象為鍵,存儲線程私有的變量。
  • 數(shù)據(jù)隔離:線程通過自己的 ThreadLocalMap 訪問變量,不同線程之間的數(shù)據(jù)互不影響。
  • 內(nèi)存模型
Thread-1 → ThreadLocalMap → { ThreadLocalA → Value1, ThreadLocalB → Value2 }
Thread-2 → ThreadLocalMap → { ThreadLocalA → Value3, ThreadLocalB → Value4 }

4.4、注意

由于ThreadLocal里面維護(hù)了ThreadLocalMap類,如下圖所示:

而TheadLocalMap是由Entry[]組成組成,Entry[]維護(hù)了多個entry。如下所示:

一個entry由key(threadlocal)和value,Entry繼承了弱引用,關(guān)于弱引用可參考:對Java 資源管理和引用體系的介紹

如下圖所示:entry

如果使用不當(dāng),會引發(fā)oom問題,主要是由GC回收機(jī)制和內(nèi)存結(jié)構(gòu)兩者引起。

可參考:就ThreadLocal使用時OOM的討論

4.5、使用場景

  • 用戶會話信息: 在 web 應(yīng)用中維護(hù)用戶的會話信息,避免將狀態(tài)信息寫到全局上下文。
  • 數(shù)據(jù)庫連接: 在線程中維護(hù)數(shù)據(jù)源連接,避免不同線程之間共享資源引起的競爭。
  • 事務(wù)管理(如 Spring 的 TransactionSynchronizationManager)。

以下是一個簡單的 Spring Bean 示例,展示如何在 Spring 中使用 ThreadLocal 來存儲用戶會話信息。

1.定義一個 ThreadLocal Storage

import org.springframework.stereotype.Component;

@Component
public class UserContext {
    private static final ThreadLocal<String> userHolder = new ThreadLocal<>();

    public void setCurrentUser(String username) {
        userHolder.set(username);
    }

    public String getCurrentUser() {
        return userHolder.get();
    }

    //清理 ThreadLocal,防止內(nèi)存泄漏
    public void clear() {
        userHolder.remove(); // 清除當(dāng)前線程中的值
    }
}

2.使用 UserContext

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserContext userContext;

    public void login(String username) {
        userContext.setCurrentUser(username);
        System.out.println("User logged in: " + userContext.getCurrentUser());
    }

    public void logout() {
        System.out.println("User logged out: " + userContext.getCurrentUser());
        userContext.clear();
    }
}

3 示例測試類

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testThreadLocal() {
        userService.login("Alice");
        userService.logout();

        // Clear (will just have no output, but it demonstrates functionality)
        userService.login("Bob");
        userService.logout();
    }
}

4. 圖形展示

在多線程環(huán)境中的 ThreadLocal 可能如下圖所示:

+-------------------+
|      UserContext  |
|-------------------|
| ThreadLocal       |
| - userHolder      |
+-------------------+
     |         |
     |         |
     v         v
+------------+ +-------------+
| Thread A   | | Thread B    |
|------------| |------------ |
| - user: "Alice" | - user: "Bob" |
+------------+ +--------------+

在每個線程中,UserContext 提供了對 ThreadLocal 變量獨(dú)立的值,使得 Thread A 可以存儲與 Thread B 不同的用戶會話信息。

5、解決方案

根據(jù)以上介紹Spring Bean的線程安全問題,以下是各種常用的解決方案。

1、同步機(jī)制去處理

synchronized 關(guān)鍵字或者 ReentrantLock 可重入鎖。

示例:

synchronized介紹:

 import org.springframework.stereotype.Component;
 ?
 @Component
 public class OrderServiceBean {
 ?
     private int orderStatus;
 ?
     public synchronized void updateOrderStatus() {
         // 這里進(jìn)行更新訂單狀態(tài)的具體業(yè)務(wù)邏輯,比如根據(jù)某些條件修改orderStatus的值
         orderStatus++;
     }
 ?
     public int getOrderStatus() {
         return orderStatus;
     }
 }

ReentrantLock介紹:

 import org.springframework.stereotype.Component;
 import java.util.concurrent.locks.ReentrantLock;
 ?
 @Component
 public class OrderServiceBean {
 ?
     private int orderStatus;
     private ReentrantLock lock = new ReentrantLock();
 ?
     public void updateOrderStatus() {
         lock.lock();
         try {
             // 這里進(jìn)行更新訂單狀態(tài)的具體業(yè)務(wù)邏輯,比如根據(jù)某些條件修改orderStatus的值
             orderStatus++;
         } finally {
             lock.unlock();
         }
     }
 ?
     public int getOrderStatus() {
         return orderStatus;
     }
 }

2、Treadlocal對象(推薦)

3、采用不可變對象(Immutable Objects)

設(shè)置final對象或者成員變量屬性。

4、使用原子類(Atomic Classes)

 import org.springframework.stereotype.Component;
 import java.util.concurrent.atomic.AtomicInteger;
 ?
 @Component
 public class VisitCountBean {
 ?
     private AtomicInteger visitCount = new AtomicInteger(0);
 ?
     public void incrementVisitCount() {
         visitCount.incrementAndGet();
     }
 ?
     public int getVisitCount() {
         return visitCount.get();
     }
 }

在 Spring 中實(shí)現(xiàn)線程安全,尤其是涉及到多個線程共享狀態(tài)時,常常需要:

  • 選擇適當(dāng)?shù)?Bean 作用域。
  • 盡量減少或避免可變狀態(tài)。
  • 使用 ThreadLocal 來管理線程局部數(shù)據(jù)。
  • 使用 AOP 及 Spring 事務(wù)來處理業(yè)務(wù)邏輯。
  • 實(shí)現(xiàn)良好的設(shè)計(jì)模式以確保代碼的可維護(hù)性。

通過以上最佳實(shí)踐,可以有效地在 Spring 應(yīng)用中實(shí)現(xiàn)線程安全,確保系統(tǒng)的穩(wěn)定性和數(shù)據(jù)一致性。

總結(jié)

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼

    SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼

    這篇文章主要介紹了SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • SpringBoot整合JWT(JSON?Web?Token)生成token與驗(yàn)證的流程及示例

    SpringBoot整合JWT(JSON?Web?Token)生成token與驗(yàn)證的流程及示例

    JSON Web Token(JWT)是一種開放的標(biāo)準(zhǔn)(RFC 7519),定義了一種緊湊的、自包含的方式來安全地在各方之間傳輸信息作為JSON對象,這篇文章主要給大家介紹了關(guān)于SpringBoot整合JWT(JSON?Web?Token)生成token與驗(yàn)證的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • 詳解Java線程中常用操作

    詳解Java線程中常用操作

    這篇文章主要為大家詳細(xì)介紹了一下Java線程中的一些常用操作,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)或工作有一定幫助,需要的可以參考一下
    2022-05-05
  • Java實(shí)現(xiàn)LRU緩存的實(shí)例詳解

    Java實(shí)現(xiàn)LRU緩存的實(shí)例詳解

    這篇文章主要介紹了Java實(shí)現(xiàn)LRU緩存的實(shí)例詳解的相關(guān)資料,這里提供實(shí)例幫助大家理解掌握這部分內(nèi)容,需要的朋友可以參考下
    2017-08-08
  • Java中實(shí)現(xiàn)WebSocket方法詳解

    Java中實(shí)現(xiàn)WebSocket方法詳解

    這篇文章主要介紹了Java中實(shí)現(xiàn)WebSocket方法詳解,WebSocket?是一種新型的網(wǎng)絡(luò)協(xié)議,它允許客戶端和服務(wù)器之間進(jìn)行雙向通信,可以實(shí)現(xiàn)實(shí)時數(shù)據(jù)交互,需要的朋友可以參考下
    2023-07-07
  • java動態(tài)目錄樹的實(shí)現(xiàn)示例

    java動態(tài)目錄樹的實(shí)現(xiàn)示例

    在開發(fā)過程中,常常需要對目錄結(jié)構(gòu)進(jìn)行操作和展示,本文主要介紹了java動態(tài)目錄樹的實(shí)現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • SLF4J報錯解決:No SLF4J providers were found的問題

    SLF4J報錯解決:No SLF4J providers were found的

    這篇文章主要介紹了SLF4J報錯解決:No SLF4J providers were found的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java讀取OpenSSL生成的PEM公鑰文件操作

    Java讀取OpenSSL生成的PEM公鑰文件操作

    這篇文章主要介紹了Java讀取OpenSSL生成的PEM公鑰文件操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • springboot省去配置Tomcat的步驟問題

    springboot省去配置Tomcat的步驟問題

    這篇文章主要介紹了springboot省去配置Tomcat的步驟問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java實(shí)現(xiàn)根據(jù)模板自動生成新的PPT

    Java實(shí)現(xiàn)根據(jù)模板自動生成新的PPT

    這篇文章主要介紹了如何利用Java代碼自動生成PPT,具體就是查詢數(shù)據(jù)庫數(shù)據(jù),然后根據(jù)模板文件(PPT),將數(shù)據(jù)庫數(shù)據(jù)與模板文件(PPT),進(jìn)行組合一下,生成新的PPT文件。感興趣的可以了解一下
    2022-02-02

最新評論