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

java多線程中線程封閉詳解

 更新時(shí)間:2019年07月16日 11:48:02   作者:Hxinguan  
在本文里我們給大家分享了關(guān)于java多線程中線程封閉的知識(shí)點(diǎn)內(nèi)容以及用法,有需要讀者們可以參考下。

線程封閉的概念

訪問共享變量時(shí),通常要使用同步,所以避免使用同步的方法就是減少共享數(shù)據(jù)的使用,這種技術(shù)就是線程封閉。

實(shí)現(xiàn)線程封閉的方法

1:ad-hoc線程封閉

這是完全靠實(shí)現(xiàn)者控制的線程封閉,他的線程封閉完全靠實(shí)現(xiàn)者實(shí)現(xiàn)。也是最糟糕的一種線程封閉。所以我們直接把他忽略掉吧。

2:棧封閉

棧封閉是我們編程當(dāng)中遇到的最多的線程封閉。什么是棧封閉呢?簡單的說就是局部變量。多個(gè)線程訪問一個(gè)方法,此方法中的局部變量都會(huì)被拷貝一分兒到線程棧中。所以局部變量是不被多個(gè)線程所共享的,也就不會(huì)出現(xiàn)并發(fā)問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發(fā)問題。

3:ThreadLocal封閉

使用ThreadLocal是實(shí)現(xiàn)線程封閉的最好方法,有興趣的朋友可以研究一下ThreadLocal的源碼,其實(shí)我們可以理解ThreadLocal內(nèi)部維護(hù)了一個(gè)Map,Map的key是每個(gè)線程的名稱,而Map的值就是我們要封閉的對象。每個(gè)線程中的對象都對應(yīng)著Map中一個(gè)值,也就是ThreadLocal利用Map實(shí)現(xiàn)了對象的線程封閉。

線程封閉詳解

線程封閉:當(dāng)訪問共享的可變數(shù)據(jù)時(shí),通常需要同步。一種避免同步的方式就是不共享數(shù)據(jù)。如果僅在單線程內(nèi)訪問數(shù)據(jù),就不需要同步,這種技術(shù)稱為線程封閉(thread confinement)

線程封閉技術(shù)一個(gè)常見的應(yīng)用就是JDBC的Connection對象,JDBC規(guī)范并沒有要求Connection對象必須是線程安全的,在服務(wù)器應(yīng)用程序中,線程從連接池獲取一個(gè)Connection對象,使用完之后將對象返還給連接池。下面介紹幾種線程封閉技術(shù):

1、Ad-hoc線程封閉

Ad-hoc線程封閉是指,維護(hù)線程的封閉性的職責(zé)完全由程序?qū)崿F(xiàn)承擔(dān),是非常脆弱的,因此在程序中盡量少使用,一般使用更強(qiáng)的線程封閉技術(shù),比如棧封閉或者ThreadLocal類。

2、棧封閉  

棧封閉是線程封閉的一種特列,在棧封閉中,只能通過局部變量才能訪問對象。局部變量的固有屬性之一就是封閉在執(zhí)行棧中,其他線程無法訪問這個(gè)棧,棧封閉也稱為線程內(nèi)部使用或者線程局部使用。簡單的說就是局部變量。多個(gè)線程訪問一個(gè)方法,此方法中的局部變量都會(huì)被拷貝一分兒到線程棧中。所以局部變量是不被多個(gè)線程所共享的,也就不會(huì)出現(xiàn)并發(fā)問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發(fā)問題。

比如下面的例子:

public int loadTheArk(Collection<Animal> candidates) { 
    SortedSet<Animal> animals; 
    int numPairs = 0; 
    Animal candidate = null; 
     
    //animals被封裝在方法中,不要使它們溢出 
    animals = new TreeSet<Animal>(new SpeciesGenderComparator()); 
    animals.addAll(candidates); 
    for(Animal a:animals){ 
      if(candidate==null || !candidate.isPotentialMate(a)){ 
        candidate = a; 
      }else{ 
        ark.load(new AnimalPair(candidate,a)); 
        ++numPairs; 
        candidate = null; 
      } 
    } 
    return numPairs; 
}

在loadTheArk中實(shí)例化一個(gè)TreeSet對象,并將該對象的一個(gè)引用保存到animals中。此時(shí),只有一個(gè)引用指向集合animals,這個(gè)引用被封閉到局部變量中,因此也被封閉到局部變量中。然而,如果發(fā)布了對集合animals(或者該對象中的任何內(nèi)部數(shù)據(jù))的引用,那么封閉性將被破壞,并導(dǎo)致對象animals的逸出。

3、ThreadLocal類

維持線程封閉性的一種更加規(guī)范方法是使用ThreadLocal類,這個(gè)類能使線程中某個(gè)值與保存值的對象關(guān)聯(lián)起來。ThreadLocal類提供了get和set等訪問接口或者方法,這些方法為每個(gè)使用該變量的線程都存在一份獨(dú)立的副本,因此get總是放回當(dāng)前執(zhí)行線程在調(diào)用set設(shè)置的最新值??匆幌孪旅娲a例子:

public class ConnectionManager {  
  private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { 
    public Connection initialValue() { 
      Connection conn = null; 
      try { 
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password"); 
      } catch (SQLException e) { 
        e.printStackTrace(); 
      } 
      return conn; 
    } 
  }; 
 
  public static Connection getConnection() { 
    return connectionHolder.get(); 
  } 
 
  public static void setConnection(Connection conn) { 
    connectionHolder.set(conn); 
  } 
}

通過調(diào)用ConnectionManager.getConnection()方法,每個(gè)線程獲取到的,都是自己獨(dú)立擁有的一個(gè)的Connection對象副本,第一次獲取時(shí),是通過initialValue()方法的返回值來設(shè)置值的。通過ConnectionManager.setConnection(Connection conn)方法設(shè)置的Connection對象,也只會(huì)和當(dāng)前線程綁定。這樣就實(shí)現(xiàn)了Connection對象在多個(gè)線程中的完全隔離。在Spring容器中管理多線程環(huán)境下的Connection對象時(shí),采用的思路和以上代碼非常相似。

每個(gè)線程是怎么和Connection對象副本綁定的?這個(gè)對象副本保存在哪里。當(dāng)某個(gè)線程初次調(diào)用ThreadLocal類的get方法時(shí),就會(huì)調(diào)用initialValue來獲取初始值,從概念上看,我們可以將ThreadLocal<T>視為包含了Map<thread, T>對象,其中保存了特定于該線程的值,但是ThreadLocal的實(shí)現(xiàn)并非如此,這樣只是為了我們方便理解而已。

下面我們來分析一下ThreadLocal類的源碼。ThreadLocal類的方法很簡單,只有四個(gè),分別為set,get,remove, initialValue,從字面上我們也能理解這些方法的作用。

public T get():返回當(dāng)前線程所對應(yīng)的局部變量。

public void set(T arg0):設(shè)置當(dāng)前線程局部變量的值?! ?/p>

public void remove():將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。注意,當(dāng)線程結(jié)束后,對應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。

protected T initialValue(): 對當(dāng)線程局部變量進(jìn)行初始化,并返回該初始值。是protected 屬性,顯然是讓子類進(jìn)行對其覆蓋重寫的,只有第一次調(diào)用set和get方法時(shí)才調(diào)用?! ?/p>

下面我們對這四個(gè)方法的源碼進(jìn)行分析,看看ThreadLocal類是如何實(shí)現(xiàn)這種“為每個(gè)線程提供不同的變量拷貝”。

3.1 set方法

以下是set方法的源碼

public void set(T arg0) {
    Thread arg1 = Thread.currentThread();
    ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);
    if (arg2 != null) {
      arg2.set(this, arg0);
    } else {
      this.createMap(arg1, arg0);
    }

  }

從set方法中可以看到,首先獲取當(dāng)前線程:Thread arg1 = Thread.currentThread();

再獲取當(dāng)前線程的ThreadLocalMap:ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);

判斷ThreadLocalMap是否為空,不為空,則以鍵值對的形式設(shè)置值,key為this,value就是局部變量的副本,this是當(dāng)前線程持有的ThreadLocal類實(shí)例化對象。

假如為空,則通過createMap方法創(chuàng)建。

我們看下getMap和createMap方法的源碼:

ThreadLocal.ThreadLocalMap getMap(Thread arg0) {
    return arg0.threadLocals;
}


void createMap(Thread arg0, T arg1) {
    arg0.threadLocals = new ThreadLocal.ThreadLocalMap(this, arg1);
  
}

從代碼上已經(jīng)寫的非常清楚,每個(gè)線程都有自己的局部變量的副本,該副本是存在ThreadLocalMap 中,其中鍵值就是ThreadLocal類實(shí)例化對象。也就是說每個(gè)線程都擁有自己的ThreadLocalMap,ThreadLocalMap保存的就是局部變量副本。我們看一下java.lang.Thread源碼。

private static int threadInitNumber;
ThreadLocalMap threadLocals = null;
ThreadLocalMap inheritableThreadLocals = null;

3.2 get方法

public T get() {
    Thread arg0 = Thread.currentThread();
    ThreadLocal.ThreadLocalMap arg1 = this.getMap(arg0);
    if (arg1 != null) {
      ThreadLocal.ThreadLocalMap.Entry arg2 = arg1.getEntry(this);
      if (arg2 != null) {
        Object arg3 = arg2.value;
        return arg3;
      }
    }

    return this.setInitialValue();
}

從代碼上看,前兩步和set方法是一個(gè)樣的,分別獲取當(dāng)前線程和當(dāng)前線程的ThreadLocalMap,第三步判斷ThreadLocalMap是否為空,不為空根據(jù)this鍵值獲取value,為空調(diào)用setInitialValue()方法。

以下是setInitialValue方法代碼:

private T setInitialValue() {
    Object arg0 = this.initialValue();
    Thread arg1 = Thread.currentThread();
    ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);
    if (arg2 != null) {
      arg2.set(this, arg0);
    } else {
      this.createMap(arg1, arg0);
    }

    return arg0;
}

在setInitialValue里調(diào)用了initialValue()方法,也就是子類要重寫覆蓋的方法,對應(yīng)上面的例子的代碼是:

protected Connection initialValue() { 
      Connection conn = null; 
      try { 
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password"); 
      } catch (SQLException e) { 
        e.printStackTrace(); 
      } 
      return conn; 
}

然后獲取當(dāng)前線程和當(dāng)前線程的ThreadLocalMap,ThreadLocalMap為空則調(diào)用createMap,否則調(diào)用set方法。

3.3 總結(jié)

ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵進(jìn)行查找的,這當(dāng)然和前面set()方法的代碼是相呼應(yīng)的。

進(jìn)一步地,我們可以創(chuàng)建不同的ThreadLocal實(shí)例來實(shí)現(xiàn)多個(gè)變量在不同線程間的訪問隔離,為什么可以這么做?因?yàn)椴煌腡hreadLocal對象作為不同鍵,當(dāng)然也可以在線程的ThreadLocalMap對象中設(shè)置不同的值了。通過ThreadLocal對象,在多線程中共享一個(gè)值和多個(gè)值的區(qū)別,就像你在一個(gè)HashMap對象中存儲(chǔ)一個(gè)鍵值對和多個(gè)鍵值對一樣,僅此而已。

也就說,每個(gè)線程都有一個(gè)ThreadLocalMap,該線程訪問到某個(gè)局部變量,且該局部變量是用ThreadLocal類進(jìn)行聲明時(shí),該線程就會(huì)new ThreadLocal(),然后將該ThreadLocal類的對象作為key值,所對應(yīng)的局部變量作為value值保存到ThreadLocalMap中。當(dāng)線程訪問多個(gè)ThreadLocal類進(jìn)行聲明局部變量時(shí),在ThreadLocalMap中就有多個(gè)鍵值對。而每個(gè)線程都有自己的ThreadLocalMap,從而達(dá)到隔離的目的了。

當(dāng)某個(gè)線程終止后,該線程里的ThreadLocalMap也被回收了,所以完全不用擔(dān)心內(nèi)存泄漏的問題。

假如多線程訪問的對象實(shí)例是單例的,或者說只能創(chuàng)建一個(gè),那就老老實(shí)實(shí)的使用同步機(jī)制(synchronized)了.

相關(guān)文章

  • spring如何解決循環(huán)依賴問題詳解

    spring如何解決循環(huán)依賴問題詳解

    這篇文章主要給大家介紹了關(guān)于spring如何解決循環(huán)依賴問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • RateLimiter 源碼分析

    RateLimiter 源碼分析

    本文主要對ratelimiter的常用方法以及源碼進(jìn)行了分析解讀,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-09-09
  • IDEA快速部署Spring?Boot?項(xiàng)目到Docker的實(shí)現(xiàn)方法

    IDEA快速部署Spring?Boot?項(xiàng)目到Docker的實(shí)現(xiàn)方法

    本文主要介紹了IDEA快速部署Spring?Boot?項(xiàng)目到Docker的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • springboot配合Thymeleaf完美實(shí)現(xiàn)遍歷功能

    springboot配合Thymeleaf完美實(shí)現(xiàn)遍歷功能

    Thymeleaf顯然是一個(gè)開發(fā)頁面的技術(shù),現(xiàn)在各種前端技術(shù)層出不窮,比如現(xiàn)在主流的Vue、React、AngularJS等。這篇文章主要介紹了springboot配合Thymeleaf完美實(shí)現(xiàn)遍歷,需要的朋友可以參考下
    2021-09-09
  • json序列化時(shí)忽略值為null的字段2種方式實(shí)例

    json序列化時(shí)忽略值為null的字段2種方式實(shí)例

    這篇文章主要給大家介紹了關(guān)于json序列化時(shí)忽略值為null的字段的2種方式,當(dāng)對象中某個(gè)字段為null時(shí),我們希望將對象轉(zhuǎn)換成json時(shí)為null的字段不會(huì)被轉(zhuǎn)換到j(luò)son字符串,里面需要的朋友可以參考下
    2023-10-10
  • MybatisPlusException:Failed?to?process,Error?SQL異常報(bào)錯(cuò)的解決辦法

    MybatisPlusException:Failed?to?process,Error?SQL異常報(bào)錯(cuò)的解決辦法

    這篇文章主要給大家介紹了關(guān)于MybatisPlusException:Failed?to?process,Error?SQL異常報(bào)錯(cuò)的解決辦法,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-03-03
  • Java中List  Set和Map之間的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java中List Set和Map之間的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java集合的主要分為三種類型set集,list列表,map映射,接下來通過本文給大家詳細(xì)介紹java中l(wèi)ist、Set和Map之間的區(qū)別,需要的的朋友參考下吧
    2017-05-05
  • IDEA中Git的基本應(yīng)用詳解

    IDEA中Git的基本應(yīng)用詳解

    這篇文章主要介紹了IDEA中Git的基本應(yīng)用詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • 一文詳解SpringBoot中CommandLineRunner接口

    一文詳解SpringBoot中CommandLineRunner接口

    Spring Boot的CommandLineRunner接口是一個(gè)函數(shù)式接口,用于在Spring Boot應(yīng)用程序啟動(dòng)后執(zhí)行一些初始化操作,它提供了一個(gè)run方法,該方法在應(yīng)用程序啟動(dòng)后被調(diào)用,本文給大家詳細(xì)介紹了SpringBoot中CommandLineRunner接口,需要的朋友可以參考下
    2023-10-10
  • WebSocket實(shí)現(xiàn)系統(tǒng)后臺(tái)消息實(shí)時(shí)通知功能

    WebSocket實(shí)現(xiàn)系統(tǒng)后臺(tái)消息實(shí)時(shí)通知功能

    在現(xiàn)代Web應(yīng)用中,提供實(shí)時(shí)通知對于改善用戶體驗(yàn)至關(guān)重要,WebSocket技術(shù)允許建立雙向通信通道,從系統(tǒng)后臺(tái)將消息實(shí)時(shí)傳送給系統(tǒng)用戶,下面我們就來深入探討一下如何使用WebSocket來實(shí)現(xiàn)這一功能吧
    2023-10-10

最新評論