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

Spring在SingleTon模式下的線程安全詳解

 更新時(shí)間:2022年01月19日 15:14:58   作者:GeorgiaStar  
這篇文章主要介紹了Spring在SingleTon模式下的線程安全詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

1、有狀態(tài)的bean與無(wú)狀態(tài)的bean

  • 有狀態(tài)bean:每個(gè)用戶有自己特有的一個(gè)實(shí)例,在用戶的生存期內(nèi),bean保存了用戶的信息,即有狀態(tài);一旦用戶滅亡(調(diào)用結(jié)束或?qū)嵗Y(jié)束),bean的生命期也告結(jié)束。即每個(gè)用戶最初都會(huì)得到一個(gè)初始的bean。
  • 無(wú)狀態(tài)bean:bean一旦實(shí)例化就被加進(jìn)會(huì)話池中,各個(gè)用戶都可以共用。即使用戶已經(jīng)消亡,bean的生命期也不一定結(jié)束,它可能依然存在于會(huì)話池中,供其他用戶調(diào)用。由于沒(méi)有特定的用戶,那么也就不能保持某一用戶的狀態(tài),所以叫無(wú)狀態(tài)bean。但無(wú)狀態(tài)會(huì)話bean 并非沒(méi)有狀態(tài),如果它有自己的屬性(變量)。

有狀態(tài)就是有數(shù)據(jù)存儲(chǔ)功能。有狀態(tài)對(duì)象(Stateful Bean),就是有實(shí)例變量的對(duì)象 ,可以保存數(shù)據(jù),是非線程安全的。在不同方法調(diào)用間不保留任何狀態(tài)。

無(wú)狀態(tài)就是一次操作不能保存數(shù)據(jù)。無(wú)狀態(tài)對(duì)象(Stateless Bean),就是沒(méi)有實(shí)例變量的對(duì)象 ,不能保存數(shù)據(jù)是不變類,是線程安全的。

在Spring的Bean配置中,存在這樣兩種情況:

<bean id="testManager" class="com.sw.TestManagerImpl" scope="singleton" /> ?
<bean id="testManager" class="com.sw.TestManagerImpl" scope="prototype" />?

當(dāng)然,scope的值不止這兩種,還包括了request、session 等。但用的最多的還是singleton單態(tài)與prototype多態(tài)。

singleton表示該bean全局只有一個(gè)實(shí)例,Spring中bean的scope默認(rèn)也是singleton。

prototype表示該bean在每次被注入的時(shí)候,都要重新創(chuàng)建一個(gè)實(shí)例,這種情況適用于有狀態(tài)的Bean。

下面是一個(gè)有狀態(tài)的Bean示例

public class TestManagerImpl implements TestManager { ?
?? ?private User user;
?? ?public void deleteUser(User e) throws Exception { ?
? ? ? ? user = e;?? ?//1
? ? ? ? prepareData(e);
? ? }
? ? public void prepareData(User e) throws Exception { ?
?? ??? ?user = getUserByID(e.getId()); ?? ?//2
?? ??? ?//使用user.getId();?? ??? ??? ??? ?//3
? ? }
}

如果該Bean配置為singleton,會(huì)出現(xiàn)什么樣的狀況呢?

如果有2個(gè)用戶訪問(wèn),都調(diào)用到了該Bean,假定為user1、user2。

當(dāng)user1調(diào)用到程序中的步驟1的時(shí)候,該Bean的私有變量user被賦值為user1,當(dāng)user1的程序走到步驟2的時(shí)候,該Bean的私有變量user被重新賦值為user1_create,理想的狀況,當(dāng)user1走到3步驟的時(shí)候,私有變量user應(yīng)該為user1_create;但如果在user1調(diào)用到3步驟之前,user2開(kāi)始運(yùn)行到了步驟1了,由于單態(tài)的資源共享,則私有變量user被修改為user2;這種情況下,user1的步驟3用到的user.getId()實(shí)際用到是user2的對(duì)象。

即將有狀態(tài)的bean配置成singleton會(huì)造成資源混亂問(wèn)題(線程安全問(wèn)題),而如果是prototype的話,就不會(huì)出現(xiàn)資源共享的問(wèn)題,即不會(huì)出現(xiàn)線程安全的問(wèn)題。

注:如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,那么代碼就是線程安全的。

通過(guò)上面分析,大家已經(jīng)對(duì)有狀態(tài)和無(wú)狀態(tài)有了一定的理解。無(wú)狀態(tài)的Bean適合用不變模式,技術(shù)就是單例模式,這樣可以共享實(shí)例,提高性能。有狀態(tài)的Bean,多線程環(huán)境下不安全,那么適合用Prototype原型模式(解決多線程問(wèn)題),每次對(duì)bean的請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的bean實(shí)例。

2、Spring中的單例

Spring中的單例與設(shè)計(jì)模式里面的單例略有不同,設(shè)計(jì)模式的單例是在整個(gè)應(yīng)用中只有一個(gè)實(shí)例,而Spring中的單例是在一個(gè)IOC容器中就只有一個(gè)實(shí)例。

大多數(shù)時(shí)候客戶端都在訪問(wèn)我們應(yīng)用中的業(yè)務(wù)對(duì)象,為了減少并發(fā)控制,在這個(gè)時(shí)候我們不應(yīng)該在業(yè)務(wù)對(duì)象中設(shè)置那些容易造成出錯(cuò)的成員變量。在并發(fā)訪問(wèn)時(shí)候,這些成員變量將會(huì)是并發(fā)線程中的共享對(duì)象,那么這個(gè)時(shí)候就會(huì)出現(xiàn)意外情況。

成員變量的解決方式:

  • 方法的參數(shù)局部變量(在方法中new)
  • 使用Threadlocal
  • 設(shè)置bean的scope=prototype

3、Spring使用ThreadLocal解決線程安全問(wèn)題案例

Spring作為一個(gè)IOC容器,幫助我們管理了許許多多的bean。但其實(shí),Spring并沒(méi)有保證這些對(duì)象的線程安全,需要由開(kāi)發(fā)者自己編寫(xiě)解決線程安全問(wèn)題的代碼。

在使用Spring時(shí),很多人可能對(duì)Spring中為什么DAO和Service對(duì)象采用單實(shí)例方式很迷惑,這些讀者是這么認(rèn)為的。

DAO對(duì)象必須包含一個(gè)數(shù)據(jù)庫(kù)的連接Connection,而這個(gè)Connection不是線程安全的,所以每個(gè)DAO都要包含一個(gè)不同的Connection對(duì)象實(shí)例,這樣一來(lái)DAO對(duì)象就不能是單實(shí)例的了。

上述觀點(diǎn)對(duì)了一半。對(duì)的是“每個(gè)DAO都要包含一個(gè)不同的Connection對(duì)象實(shí)例”這句話,錯(cuò)的是“DAO對(duì)象就不能是單實(shí)例”。其實(shí)Spring在實(shí)現(xiàn)Service和DAO對(duì)象時(shí),使用了ThreadLocal這個(gè)類,這個(gè)是一切的核心!

  • ThreadLocal
  • 每個(gè)線程中都有一個(gè)自己的ThreadLocalMap類對(duì)象,可以將線程自己的對(duì)象保持到其中,各管各的,線程可以正確的訪問(wèn)到自己的對(duì)象。
  • 將一個(gè)共用的ThreadLocal靜態(tài)實(shí)例作為key,將不同對(duì)象的引用保存到不同線程的ThreadLocalMap中,然后在線程執(zhí)行的各處通過(guò)這個(gè)靜態(tài)ThreadLocal實(shí)例的get()方法取得自己線程保存的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象作為參數(shù)傳遞的麻煩。

一般的Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層三個(gè)層次,在不同的層中編寫(xiě)對(duì)應(yīng)的邏輯,下層通過(guò)接口向上層開(kāi)放功能調(diào)用。在一般情況下,從接收請(qǐng)求到返回響應(yīng)所經(jīng)過(guò)的所有程序調(diào)用都同屬于一個(gè)線程。這樣你就可以根據(jù)需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請(qǐng)求響應(yīng)的調(diào)用線程中,所有關(guān)聯(lián)的對(duì)象引用到的都是同一個(gè)變量。

下面的實(shí)例能夠體現(xiàn)Spring對(duì)有狀態(tài)Bean的改造思路:

public class TopicDao {
?? ?//①一個(gè)非線程安全的變量
  private Connection conn;
?? ?//②引用非線程安全變量
  public void addTopic() {
?? ??? ?Statement stat = conn.createStatement();
  }
}

由于①處的conn是成員變量,因?yàn)閍ddTopic()方法是非線程安全的,必須在使用時(shí)創(chuàng)建一個(gè)新TopicDao實(shí)例(非singleton)。下面使用ThreadLocal對(duì)conn這個(gè)非線程安全的狀態(tài)進(jìn)行改造:

public class TopicDao { ?
? ? //①使用ThreadLocal保存Connection變量 ?
? ? private static ThreadLocal <Connection>connThreadLocal = newThreadLocal<Connection>(); ?
? ? public static Connection getConnection() { ?
? ? ? ?// ②如果connThreadLocal沒(méi)有本線程對(duì)應(yīng)的Connection創(chuàng)建一個(gè)新的Connection, ?
? ? ? ?// 并將其保存到線程本地變量中。 ?
? ? ? ?if (connThreadLocal.get() == null) { ?
? ? ? ? ? ?Connection conn = getConnection(); ?
? ? ? ? ? ?connThreadLocal.set(conn); ?
? ? ? ? ? ?return conn; ?
? ? ? ?}
? ? ? ?// ③直接返回線程本地變量
? ? ? ?return connThreadLocal.get(); ?
? ? }
? ? public void addTopic() { ?
? ? ? ?// ④從ThreadLocal中獲取線程對(duì)應(yīng)的Connection ?
? ? ? ?try {
? ? ? ? ? ?Statement stat = getConnection().createStatement(); ?
? ? ? ?} catch (SQLException e) { ?
? ? ? ? ? ?e.printStackTrace(); ?
? ? ? ?}
? ? }
}

不同的線程在使用TopicDao時(shí),先判斷connThreadLocal是否是null,如果是null,則說(shuō)明當(dāng)前線程還沒(méi)有對(duì)應(yīng)的Connection對(duì)象,這時(shí)創(chuàng)建一個(gè)Connection對(duì)象并添加到本地線程變量中;如果不為null,則說(shuō)明當(dāng)前的線程已經(jīng)擁有了Connection對(duì)象,直接使用就可以了。這樣,就保證了不同的線程使用線程相關(guān)的Connection,而不會(huì)使用其它線程的Connection。因此,這個(gè)TopicDao就可以做到singleton共享了。

Spring中DAO和Service都是以單實(shí)例的bean形式存在,Spring通過(guò)ThreadLocal類將有狀態(tài)的變量(例如數(shù)據(jù)庫(kù)連接Connection)本地線程化,從而做到多線程狀況下的安全。在一次請(qǐng)求響應(yīng)的處理線程中, 該線程貫通展示、服務(wù)、數(shù)據(jù)持久化三層,通過(guò)ThreadLocal使得所有關(guān)聯(lián)的對(duì)象引用到的都是同一個(gè)變量。

當(dāng)然,這個(gè)例子本身很粗糙,將Connection的ThreadLocal直接放在DAO只能做到本DAO的多個(gè)方法共享Connection時(shí)不發(fā)生線程安全問(wèn)題,但無(wú)法和其它DAO共用同一個(gè)Connection,要做到同一事務(wù)多DAO共享同一Connection,必須在一個(gè)共同的外部類使用ThreadLocal保存Connection。

Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層,controller中引入xxxService作為成員變量,xxxService中又引入xxxDao作為成員變量,這些對(duì)象都是單例而且會(huì)被多個(gè)線程并發(fā)訪問(wèn),可我們?cè)L問(wèn)的是它們里面的方法,這些類里面通常不會(huì)含有成員變量,dao實(shí)例是在MyBatis等ORM框架里面封裝好的,已經(jīng)被測(cè)試,不會(huì)出現(xiàn)線程同步問(wèn)題了。所以出問(wèn)題的地方就是我們自己系統(tǒng)里面的業(yè)務(wù)對(duì)象,所以我們一定要注意自定義的業(yè)務(wù)對(duì)象里面千萬(wàn)不能出現(xiàn)獨(dú)立成員變量,否則會(huì)有線程安全問(wèn)題。

通常我們?cè)趹?yīng)用中的業(yè)務(wù)對(duì)象如下例子,controller中擁有成員變量list和paperService。

public class TestPaperController extends BaseController {
?? ?private static final int list = 0;
?? ?@Autowired
?? ?@Qualifier("papersService")
?? ?private TestPaperService papersService ;
?? ?public Page queryPaper(int pageSize, int page,TestPaper paper) throws EicException {
?? ? ?RowSelection localRowSelection = getRowSelection(pageSize, page);
?? ? ?List<TestPaper> paperList = papersService.queryPaper(paper,localRowSelection);
?? ? ?Page localPage = new Page(page, localRowSelection.getTotalRows(), paperList);
?? ? ?return localPage;
?? ?}
}

service里面又引入了成員變量ibatisEntityDao

@SuppressWarnings("unchecked")
@Service("papersService")
@Transactional(rollbackFor = {Exception.class})
public class TestPaperServiceImpl implements TestPaperService {
?? ?@Autowired
?? ?@Qualifier("ibatisEntityDao")
?? ?private IbatisEntityDao ibatisEntityDao;
?? ?private static final String NAMESPACE_TESTPAPER = "com.its.exam.testpaper.model.TestPaper";
?? ?private static final String BO_NAME[] = { "試卷倉(cāng)庫(kù)" };
?? ?private static final String BO_NAME2[] = { "試卷配置試題" };
?? ?private static final String BO_NAME1[] = { "試卷試題類型" };
?? ?private static final String NAMESPACE_TESTQUESTION="com.its.exam.testpaper.model.TestQuestion";
?? ?public List<TestPaper> queryPaper(TestPaper paper,RowSelection paramRowSelection) throws EicException {
?? ? ?try {
?? ? ? return (List<TestPaper>) ibatisEntityDao.queryForListWithPage(NAMESPACE_TESTPAPER, "queryPaper", paper,paramRowSelection);
?? ? ?} catch (Exception e) {
?? ? ? e.printStackTrace();
?? ? ? throw new EicException(e, "eic", "0001", BO_NAME);
?? ? ?}
?? ?}
}

由上面例子可以看出,雖然我們這個(gè)應(yīng)用里面含有成員變量,但是并不會(huì)出現(xiàn)線程同步方面的問(wèn)題,controller里面的成員變量papersService被注入后,是為了訪問(wèn)該service類的方法,papersService里面注入的成員變量ibatisEntityDao是ORM框架封裝好的,其線程同步問(wèn)題已解決。

4、ThreadLocal與線程同步機(jī)制的比較

ThreadLocal和線程同步機(jī)制相比有什么優(yōu)勢(shì)呢?ThreadLocal和線程同步機(jī)制都是為了解決多線程中相同變量的訪問(wèn)沖突問(wèn)題。

在同步機(jī)制中,通過(guò)對(duì)象的鎖機(jī)制保證同一時(shí)間只有一個(gè)線程訪問(wèn)變量。這時(shí)該變量是多個(gè)線程共享的,使用同步機(jī)制要求程序慎密地分析什么時(shí)候?qū)ψ兞窟M(jìn)行讀寫(xiě),什么時(shí)候需要鎖定某個(gè)對(duì)象,什么時(shí)候釋放對(duì)象鎖等繁雜的問(wèn)題,程序設(shè)計(jì)和編寫(xiě)難度相對(duì)較大。

而ThreadLocal則從另一個(gè)角度來(lái)解決多線程的并發(fā)訪問(wèn)。ThreadLocal會(huì)為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本,從而隔離了多個(gè)線程對(duì)數(shù)據(jù)的訪問(wèn)沖突。因?yàn)槊恳粋€(gè)線程都擁有自己的變量副本,從而也就沒(méi)有必要對(duì)該變量進(jìn)行同步了。ThreadLocal提供了線程安全的共享對(duì)象,在編寫(xiě)多線程代碼時(shí),可以把不安全的變量封裝進(jìn)ThreadLocal。

由于ThreadLocal中可以持有任何類型的對(duì)象,低版本JDK所提供的get()返回的是Object對(duì)象,需要強(qiáng)制類型轉(zhuǎn)換。但JDK 5.0通過(guò)泛型很好的解決了這個(gè)問(wèn)題,在一定程度地簡(jiǎn)化ThreadLocal的使用。

概括起來(lái)說(shuō),對(duì)于多線程資源共享的問(wèn)題,同步機(jī)制采用了以時(shí)間換空間”的方式,而ThreadLocal采用了以空間換時(shí)間的方式。前者僅提供一份變量,讓不同的線程排隊(duì)訪問(wèn),而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而互不影響。

ThreadLocal是解決線程安全問(wèn)題一個(gè)很好的思路,它通過(guò)為每個(gè)線程提供一個(gè)獨(dú)立的變量副本解決了變量并發(fā)訪問(wèn)的沖突問(wèn)題。在很多情況下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問(wèn)題更簡(jiǎn)單,更方便,且結(jié)果程序擁有更高的并發(fā)性。

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

相關(guān)文章

  • spring初始化源碼代碼淺析

    spring初始化源碼代碼淺析

    Spring框架被廣泛應(yīng)用于我們的日常工作中,但是很長(zhǎng)時(shí)間以來(lái)我們都是只會(huì)使用,不懂它的作用原理,下面這篇文章主要給大家介紹了關(guān)于spring初始化源碼的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • Java實(shí)現(xiàn)的校驗(yàn)銀行卡功能示例

    Java實(shí)現(xiàn)的校驗(yàn)銀行卡功能示例

    這篇文章主要介紹了Java實(shí)現(xiàn)的校驗(yàn)銀行卡功能,結(jié)合完整實(shí)例形式分析了java針對(duì)銀行卡類型、歸屬地等信息的判斷、讀取相關(guān)操作技巧,需要的朋友可以參考下
    2018-06-06
  • SpringBoot實(shí)現(xiàn)短信發(fā)送及手機(jī)驗(yàn)證碼登錄

    SpringBoot實(shí)現(xiàn)短信發(fā)送及手機(jī)驗(yàn)證碼登錄

    本文主要介紹了SpringBoot實(shí)現(xiàn)短信發(fā)送及手機(jī)驗(yàn)證碼登錄,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見(jiàn)的使用方法總結(jié)

    mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見(jiàn)的使用方法總結(jié)

    mybatis-plus是在mybatis的基礎(chǔ)上做增強(qiáng)不做改變,簡(jiǎn)化了CRUD操作,下面這篇文章主要給大家介紹了關(guān)于mybatis-plus中l(wèi)ambdaQuery()與lambdaUpdate()比較常見(jiàn)的使用方法,需要的朋友可以參考下
    2022-09-09
  • Java SpringMVC實(shí)現(xiàn)PC端網(wǎng)頁(yè)微信掃碼支付(完整版)

    Java SpringMVC實(shí)現(xiàn)PC端網(wǎng)頁(yè)微信掃碼支付(完整版)

    這篇文章主要介紹了Java SpringMVC實(shí)現(xiàn)PC端網(wǎng)頁(yè)微信掃碼支付(完整版)的相關(guān)資料,非常不錯(cuò)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2016-11-11
  • 最新評(píng)論