<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>

          并發(fā)編程和ThreadLocal

          共 28666字,需瀏覽 58分鐘

           ·

          2021-06-21 21:03

          點(diǎn)擊上方 Java學(xué)習(xí)之道,選擇 設(shè)為星標(biāo)

          每天18:30點(diǎn),干貨準(zhǔn)時(shí)奉上!

          來源: cnblogs.com/xiaoxi/p/7755253.htmll
          作者: 平凡希

          Part1對(duì)ThreadLocal的理解

          ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲(chǔ),其實(shí)意思差不多??赡芎芏嗯笥讯贾繲hreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本,那么每個(gè)線程可以訪問自己內(nèi)部的副本變量。 這句話從字面上看起來很容易理解,但是真正理解并不是那么容易。

          ThreadLocal的官方API解釋為:

          "該類提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對(duì)應(yīng)物,因?yàn)樵L問某個(gè)變量(通過其 get 或 set 方法)的每個(gè)線程都有自己的局部變量,它獨(dú)立于變量的初始化副本。ThreadLocal 實(shí)例通常是類中的 private static 字段,它們希望將狀態(tài)與某一個(gè)線程(例如,用戶 ID 或事務(wù) ID)相關(guān)聯(lián)。"

          大概的意思有兩點(diǎn):

          • 1、ThreadLocal提供了一種訪問某個(gè)變量的特殊方式:訪問到的變量屬于當(dāng)前線程,即保證每個(gè)線程的變量不一樣,而同一個(gè)線程在任何地方拿到的變量都是一致的,這就是所謂的線程隔離。


            1. 如果要使用ThreadLocal,通常定義為private static類型,在我看來最好是定義為private static final類型。

          很多博客都這樣說:ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路;ThreadLocal的目的是為了解決多線程訪問資源時(shí)的共享問題。如果你也這樣認(rèn)為的,那現(xiàn)在給你10秒鐘,清空之前對(duì)ThreadLocal的錯(cuò)誤的認(rèn)知!

          ThreadLocal可以總結(jié)為一句話:ThreadLocal的作用是提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或者組件之間一些公共變量的傳遞的復(fù)雜度。

          我們還是先來看一個(gè)例子:

          class ConnectionManager {
               
              private static Connection connect = null;
               
              public static Connection openConnection() {
                  if(connect == null){
                      connect = DriverManager.getConnection();
                  }
                  return connect;
              }
               
              public static void closeConnection() {
                  if(connect!=null)
                      connect.close();
              }
          }

          假設(shè)有這樣一個(gè)數(shù)據(jù)庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?很顯然,在多線程中使用會(huì)存在線程安全問題:第一,這里面的2個(gè)方法都沒有進(jìn)行同步,很可能在openConnection方法中會(huì)多次創(chuàng)建connect;第二,由于connect是共享變量,那么必然在調(diào)用connect的地方需要使用到同步來保障線程安全,因?yàn)楹芸赡芤粋€(gè)線程在使用connect進(jìn)行數(shù)據(jù)庫操作,而另外一個(gè)線程調(diào)用closeConnection關(guān)閉鏈接。

          所以出于線程安全的考慮,必須將這段代碼的兩個(gè)方法進(jìn)行同步處理,并且在調(diào)用connect的地方需要進(jìn)行同步處理。這樣將會(huì)大大影響程序執(zhí)行效率,因?yàn)橐粋€(gè)線程在使用connect進(jìn)行數(shù)據(jù)庫操作的時(shí)候,其他線程只有等待。

          那么大家來仔細(xì)分析一下這個(gè)問題,這地方到底需不需要將connect變量進(jìn)行共享?事實(shí)上,是不需要的。假如每個(gè)線程中都有一個(gè)connect變量,各個(gè)線程之間對(duì)connect變量的訪問實(shí)際上是沒有依賴關(guān)系的,即一個(gè)線程不需要關(guān)心其他線程是否對(duì)這個(gè)connect進(jìn)行了修改的。

          到這里,可能會(huì)有朋友想到,既然不需要在線程之間共享這個(gè)變量,可以直接這樣處理,在每個(gè)需要使用數(shù)據(jù)庫連接的方法中具體使用時(shí)才創(chuàng)建數(shù)據(jù)庫鏈接,然后在方法調(diào)用完畢再釋放這個(gè)連接。比如下面這樣:

          class ConnectionManager {
               
              private  Connection connect = null;
               
              public Connection openConnection() {
                  if(connect == null){
                      connect = DriverManager.getConnection();
                  }
                  return connect;
              }
               
              public void closeConnection() {
                  if(connect!=null)
                      connect.close();
              }
          }
           
           
          class Dao{
              public void insert() {
                  ConnectionManager connectionManager = new ConnectionManager();
                  Connection connection = connectionManager.openConnection();
                   
                  //使用connection進(jìn)行操作
                   
                  connectionManager.closeConnection();
              }
          }

          這樣處理確實(shí)也沒有任何問題,由于每次都是在方法內(nèi)部創(chuàng)建的連接,那么線程之間自然不存在線程安全問題。但是這樣會(huì)有一個(gè)致命的影響:導(dǎo)致服務(wù)器壓力非常大,并且嚴(yán)重影響程序執(zhí)行性能。由于在方法中需要頻繁地開啟和關(guān)閉數(shù)據(jù)庫連接,這樣不盡嚴(yán)重影響程序執(zhí)行效率,還可能導(dǎo)致服務(wù)器壓力巨大。

          那么這種情況下使用ThreadLocal是再適合不過的了,因?yàn)門hreadLocal在每個(gè)線程中對(duì)該變量會(huì)創(chuàng)建一個(gè)副本,即每個(gè)線程內(nèi)部都會(huì)有一個(gè)該變量,且在線程內(nèi)部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會(huì)嚴(yán)重影響程序執(zhí)行性能。

          但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由于在每個(gè)線程中都創(chuàng)建了副本,所以要考慮它對(duì)資源的消耗,比如內(nèi)存的占用會(huì)比不使用ThreadLocal要大。

          Part2深入解析ThreadLocal類

          在上面談到了對(duì)ThreadLocal的一些理解,那我們下面來看一下具體ThreadLocal是如何實(shí)現(xiàn)的。先了解一下ThreadLocal類提供的幾個(gè)方法:

          public T get() { }
          public void set(T value) { }
          public void remove() { }
          protected T initialValue() { }

          get()方法是用來獲取ThreadLocal在當(dāng)前線程中保存的變量副本,set()用來設(shè)置當(dāng)前線程中變量的副本,remove()用來移除當(dāng)前線程中變量的副本,initialValue()是一個(gè)protected方法,用來返回此線程局部變量的當(dāng)前線程的初始值,一般是在使用時(shí)進(jìn)行重寫的,它是一個(gè)延遲加載方法, 下面會(huì)詳細(xì)說明。

          首先我們來看一下ThreadLocal類是如何為每個(gè)線程創(chuàng)建一個(gè)變量的副本的。先看下get方法的實(shí)現(xiàn):

          public T get() {
              //1.首先獲取當(dāng)前線程
              Thread t = Thread.currentThread();
              //2.獲取線程的map對(duì)象
              ThreadLocalMap map = getMap(t);
              //3.如果map不為空,以threadlocal實(shí)例為key獲取到對(duì)應(yīng)Entry,然后從Entry中取出對(duì)象即可。
              if (map != null) {
                  ThreadLocalMap.Entry e = map.getEntry(this);
                  if (e != null)
                      return (T)e.value;
              }
              //如果map為空,也就是第一次沒有調(diào)用set直接get(或者調(diào)用過set,又調(diào)用了remove)時(shí),為其設(shè)定初始值
              return setInitialValue();
          }

          第3行是取得當(dāng)前線程,然后通過getMap(t)方法獲取到一個(gè)map,map的類型為ThreadLocalMap。然后接著下面獲取到<key,value>鍵值對(duì),注意這里獲取鍵值對(duì)傳進(jìn)去的是  this,而不是當(dāng)前線程t。如果獲取成功,則返回value值。如果map為空,則調(diào)用setInitialValue方法返回value。

          下面我們對(duì)上面的每一句來仔細(xì)分析。首先看一下getMap方法中做了什么:

          ThreadLocalMap getMap(Thread t) {
              return t.threadLocals;
          }

          可能大家沒有想到的是,在getMap中,是調(diào)用當(dāng)期線程t,返回當(dāng)前線程t中的一個(gè)成員變量threadLocals。那么我們繼續(xù)取Thread類中取看一下成員變量threadLocals是什么:

          /* ThreadLocal values pertaining to this thread. This map is maintained
           * by the ThreadLocal class. */

          ThreadLocal.ThreadLocalMap threadLocals = null;

          實(shí)際上就是一個(gè)ThreadLocalMap,這個(gè)類型是ThreadLocal類的一個(gè)內(nèi)部類,我們繼續(xù)取看ThreadLocalMap的實(shí)現(xiàn):可以看到ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。

          總結(jié)

          get()方法的第3和第5行很明顯是獲取屬于當(dāng)前線程的ThreadLocalMap,如果這個(gè)map不為空,我們就以當(dāng)前的ThreadLocal為鍵,去獲取相應(yīng)的Entry,Entry是ThreadLocalMap的靜態(tài)內(nèi)部類,它繼承于弱引用,所以在get()方法里面如第10行一樣調(diào)用e.value方法就可以獲取實(shí)際的資源副本值。但是如果有一個(gè)為空,說明屬于該線程的資源副本還不存在,則需要去創(chuàng)建資源副本,從代碼中可以看到是調(diào)用setInitialValue()方法,其定義如下:

          /**
           * Variant of set() to establish initialValue. Used instead
           * of set() in case user has overridden the set() method.
           *
           * @return the initial value
           */

          private T setInitialValue() {
              T value = initialValue();
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
              return value;
          }

          第8行調(diào)用initialValue()方法初始化一個(gè)值。接下來是判斷線程的ThreadLocalMap是否為空,不為空就直接設(shè)置值(鍵為this,值為value),為空則創(chuàng)建一個(gè)Map,調(diào)用方法為createMap(),其定義如下:

          void createMap(Thread t, T firstValue) {
              t.threadLocals = new ThreadLocalMap(this, firstValue);
          }

          簡(jiǎn)單明了,而ThreadLocalMap的這個(gè)構(gòu)造方法的實(shí)現(xiàn)如下:

          /**
           * Construct a new map initially containing (firstKey, firstValue).
           * ThreadLocalMaps are constructed lazily, so we only create
           * one when we have at least one entry to put in it.
           */

          ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
              table = new Entry[INITIAL_CAPACITY];
              int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
              table[i] = new Entry(firstKey, firstValue);
              size = 1;
              setThreshold(INITIAL_CAPACITY);
          }

          實(shí)例化table數(shù)組用于存儲(chǔ)鍵值對(duì),然后通過映射將鍵值對(duì)存儲(chǔ)進(jìn)入相應(yīng)的位置。

          下面再來看set方法。

          /** 
           * Sets the current thread's copy of this thread-local variable 
           * to the specified value.  Most subclasses will have no need to 
           * override this method, relying solely on the {@link #initialValue} 
           * method to set the values of thread-locals. 
           * 
           * @param value the value to be stored in the current thread's copy of 
           *        this thread-local. 
           */
            
          public void set(T value) {  
              // 獲取當(dāng)前線程對(duì)象  
              Thread t = Thread.currentThread();  
              // 獲取當(dāng)前線程本地變量Map  
              ThreadLocalMap map = getMap(t);  
              // map不為空  
              if (map != null)  
                  // 存值  
                  map.set(this, value);  
              else  
                  // 創(chuàng)建一個(gè)當(dāng)前線程本地變量Map  
                  createMap(t, value);  
          }

          在這個(gè)方法內(nèi)部我們看到,首先通過getMap(Thread t)方法獲取一個(gè)和當(dāng)前線程相關(guān)的ThreadLocalMap,然后將變量的值設(shè)置到這個(gè)ThreadLocalMap對(duì)象中,當(dāng)然如果獲取到的ThreadLocalMap對(duì)象為空,就通過createMap方法創(chuàng)建。

          至此,可能大部分朋友已經(jīng)明白了ThreadLocal是如何為每個(gè)線程創(chuàng)建變量的副本的:

          首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)。

          初始時(shí),在Thread里面,threadLocals為空,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會(huì)對(duì)Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。

          然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。

          Part3示例

          示例1:

          下面通過一個(gè)例子來證明通過ThreadLocal能達(dá)到在每個(gè)線程中創(chuàng)建變量副本的效果:

          package com.demo.test;

          public class TestThreadLocal {

              ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
              ThreadLocal<String> stringLocal = new ThreadLocal<String>();
           
              public void set() {
                  longLocal.set(Thread.currentThread().getId());
                  stringLocal.set(Thread.currentThread().getName());
              }
               
              public long getLong() {
                  return longLocal.get();
              }
               
              public String getString() {
                  return stringLocal.get();
              }
               
              public static void main(String[] args) throws InterruptedException {
                  final TestThreadLocal test = new TestThreadLocal();
                   
                  test.set();
                  System.out.println(test.getLong());
                  System.out.println(test.getString());
                   
                  Thread thread1 = new Thread(){
                      public void run() {
                          test.set();
                          System.out.println(test.getLong());
                          System.out.println(test.getString());
                      };
                  };
                  thread1.start();
                  thread1.join();
                   
                  System.out.println(test.getLong());
                  System.out.println(test.getString());
              }
          }

          這段代碼的輸出結(jié)果為:

          1

          main

          8

          Thread-0

          1

          main 從這段代碼的輸出結(jié)果可以看出,在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。最后一次在main線程再次打印副本值是為了證明在main線程中和thread1線程中的副本值確實(shí)是不同的。

          總結(jié)一下:

          • 1)實(shí)際的通過ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的;

          • 2)為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對(duì)象,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量,就像上面代碼中的longLocal和stringLocal;

          • 3)在進(jìn)行g(shù)et之前,必須先set,否則會(huì)報(bào)空指針異常。如果想在get之前不需要調(diào)用set就能正常訪問的話,必須重寫initialValue()方法。

          因?yàn)樵谏厦娴拇a分析過程中,我們發(fā)現(xiàn)如果沒有先set的話,即在map中查找不到對(duì)應(yīng)的存儲(chǔ),則會(huì)通過調(diào)用setInitialValue方法返回i,而在setInitialValue方法中,有一個(gè)語句是T value = initialValue(), 而默認(rèn)情況下,initialValue方法返回的是null。

          注意 :默認(rèn)情況下 initValue(), 返回 null 。線程在沒有調(diào)用 set 之前,第一次調(diào)用 get 的時(shí)候, get方法會(huì)默認(rèn)去調(diào)用 initValue 這個(gè)方法。所以如果沒有覆寫這個(gè)方法,可能導(dǎo)致 get 返回的是 null 。當(dāng)然如果調(diào)用過 set 就不會(huì)有這種情況了。但是往往在多線程情況下我們不能保證每個(gè)線程的在調(diào)用 get 之前都調(diào)用了set ,所以最好對(duì) initValue 進(jìn)行覆寫,以免導(dǎo)致空指針異常。

          看下面這個(gè)例子:

          package com.demo.test;

          public class TestThreadLocal {

              ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
              ThreadLocal<String> stringLocal = new ThreadLocal<String>();
           
              public void set() {
                  longLocal.set(Thread.currentThread().getId());
                  stringLocal.set(Thread.currentThread().getName());
              }
               
              public long getLong() {
                  return longLocal.get();
              }
               
              public String getString() {
                  return stringLocal.get();
              }
               
              public static void main(String[] args) throws InterruptedException {
                  final TestThreadLocal test = new TestThreadLocal();
                   
                  //test.set();
                  System.out.println(test.getLong());
                  System.out.println(test.getString());
                   
                  Thread thread1 = new Thread(){
                      public void run() {
                          test.set();
                          System.out.println(test.getLong());
                          System.out.println(test.getString());
                      };
                  };
                  thread1.start();
                  thread1.join();
                   
                  System.out.println(test.getLong());
                  System.out.println(test.getString());
              }
          }

          在main線程中,沒有先set,直接get的話,運(yùn)行時(shí)會(huì)報(bào)空指針異常。

          Exception in thread "main" java.lang.NullPointerException
              at com.demo.test.TestThreadLocal.getLong(TestThreadLocal.java:14)
              at com.demo.test.TestThreadLocal.main(TestThreadLocal.java:25)

          但是如果改成下面這段代碼,即重寫了initialValue方法:

          package com.demo.test;

          public class TestThreadLocal {

              ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){
                   protected Long initialValue() {
                       return Thread.currentThread().getId();
                   };
              };
              ThreadLocal<String> stringLocal = new ThreadLocal<String>(){
                   protected String initialValue() {
                       return Thread.currentThread().getName();
                   };
              };
           
              public void set() {
                  longLocal.set(Thread.currentThread().getId());
                  stringLocal.set(Thread.currentThread().getName());
              }
               
              public long getLong() {
                  return longLocal.get();
              }
               
              public String getString() {
                  return stringLocal.get();
              }
               
              public static void main(String[] args) throws InterruptedException {
                  final TestThreadLocal test = new TestThreadLocal();
                   
                  //test.set();
                  System.out.println(test.getLong());
                  System.out.println(test.getString());
                   
                  Thread thread1 = new Thread(){
                      public void run() {
                          test.set();
                          System.out.println(test.getLong());
                          System.out.println(test.getString());
                      };
                  };
                  thread1.start();
                  thread1.join();
                   
                  System.out.println(test.getLong());
                  System.out.println(test.getString());
              }
          }

          就可以直接不用先set而直接調(diào)用get了。

          示例2:

          package com.demo.test;

          public class TestNum {

              // ①通過匿名內(nèi)部類覆蓋ThreadLocal的initialValue()方法,指定初始值  
              private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
                  public Integer initialValue() {  
                      return 0;  
                  }  
              };  
            
              // ②獲取下一個(gè)序列值  
              public int getNextNum() {  
                  seqNum.set(seqNum.get() + 1);  
                  return seqNum.get();  
              }  
            
              public static void main(String[] args) {  
                  TestNum sn = new TestNum();  
                  // ③ 3個(gè)線程共享sn,各自產(chǎn)生序列號(hào)  
                  TestClient t1 = new TestClient(sn);  
                  TestClient t2 = new TestClient(sn);  
                  TestClient t3 = new TestClient(sn);  
                  t1.start();  
                  t2.start();  
                  t3.start();  
              }  
            
              private static class TestClient extends Thread {  
                  private TestNum sn;  
            
                  public TestClient(TestNum sn) {  
                      this.sn = sn;  
                  }  
            
                  public void run() {  
                      for (int i = 0; i < 3; i++) {  
                          // ④每個(gè)線程打出3個(gè)序列值  
                          System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["  
                                   + sn.getNextNum() + "]");  
                      }  
                  }  
              }  
          }

          通常我們通過匿名內(nèi)部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產(chǎn)生一組序列號(hào),在③處,我們生成3個(gè)TestClient,它們共享同一個(gè)TestNum實(shí)例。運(yùn)行以上代碼,在控制臺(tái)上輸出以下的結(jié)果:

          thread[Thread-0] --> sn[1]
          thread[Thread-1] --> sn[1]
          thread[Thread-2] --> sn[1]
          thread[Thread-1] --> sn[2]
          thread[Thread-0] --> sn[2]
          thread[Thread-1] --> sn[3]
          thread[Thread-2] --> sn[2]
          thread[Thread-0] --> sn[3]
          thread[Thread-2] --> sn[3]

          考察輸出的結(jié)果信息,我們發(fā)現(xiàn)每個(gè)線程所產(chǎn)生的序號(hào)雖然都共享同一個(gè)TestNum實(shí)例,但它們并沒有發(fā)生相互干擾的情況,而是各自產(chǎn)生獨(dú)立的序列號(hào),這是因?yàn)槲覀兺ㄟ^ThreadLocal為每一個(gè)線程提供了單獨(dú)的副本。

          Part4ThreadLocal的應(yīng)用場(chǎng)景

          最常見的ThreadLocal使用場(chǎng)景為 用來解決 數(shù)據(jù)庫連接、Session管理等。

          private static ThreadLocal<Connection> connectionHolder
          new ThreadLocal<Connection>() {
              public Connection initialValue() {
                  return DriverManager.getConnection(DB_URL);
              }
          };
           
          public static Connection getConnection() {
              return connectionHolder.get();
          }
          private static final ThreadLocal threadSession = new ThreadLocal();
           
          public static Session getSession() throws InfrastructureException {
              Session s = (Session) threadSession.get();
              try {
                  if (s == null) {
                      s = getSessionFactory().openSession();
                      threadSession.set(s);
                  }
              } catch (HibernateException ex) {
                  throw new InfrastructureException(ex);
              }
              return s;
          }

          ThreadLocal對(duì)象通常用于防止對(duì)可變的單實(shí)例變量或全局變量進(jìn)行共享。

          當(dāng)一個(gè)類中使用了static成員變量的時(shí)候,一定要多問問自己,這個(gè)static成員變量需要考慮線程安全嗎?也就是說,多個(gè)線程需要獨(dú)享自己的static成員變量嗎?如果需要考慮,不妨使用ThreadLocal。

          ThreadLocal的主要應(yīng)用場(chǎng)景為多線程多實(shí)例(每個(gè)線程對(duì)應(yīng)一個(gè)實(shí)例)的對(duì)象的訪問,并且這個(gè)對(duì)象很多地方都要用到。例如:同一個(gè)網(wǎng)站登錄用戶,每個(gè)用戶服務(wù)器會(huì)為其開一個(gè)線程,每個(gè)線程中創(chuàng)建一個(gè)ThreadLocal,里面存用戶基本信息等,在很多頁面跳轉(zhuǎn)時(shí),會(huì)顯示用戶信息或者得到用戶的一些信息等頻繁操作,這樣多線程之間并沒有聯(lián)系而且當(dāng)前線程也可以及時(shí)獲取想要的數(shù)據(jù)。


          ThreadLocal通常用來共享數(shù)據(jù),當(dāng)你想在多個(gè)方法中使用某個(gè)變量,這個(gè)變量是當(dāng)前線程的狀態(tài),其它線程不依賴這個(gè)變量,你第一時(shí)間想到的就是把變量定義在方法內(nèi)部,然后再方法之間傳遞參數(shù)來使用,這個(gè)方法能解決問題,但是有個(gè)煩人的地方就是,每個(gè)方法都需要聲明形參,多處聲明,多處調(diào)用。影響代碼的美觀和維護(hù)。有沒有一種方法能將變量像private static形式來訪問呢?這樣在類的任何一處地方就都能使用。這個(gè)時(shí)候ThreadLocal大顯身手了。

          Part5總結(jié)

          1實(shí)現(xiàn)思想

          ThreadLocal 的實(shí)現(xiàn)思想,我們?cè)谇懊嬉呀?jīng)說了,每個(gè)線程維護(hù)一個(gè) ThreadLocalMap 的映射表,映射表的 key 是 ThreadLocal 實(shí)例本身,value 是要存儲(chǔ)的副本變量。ThreadLocal 實(shí)例本身并不存儲(chǔ)值,它只是提供一個(gè)在當(dāng)前線程中找到副本值的 key。如下圖所示:

          2線程隔離的秘密

          線程隔離的秘密,就在于ThreadLocalMap這個(gè)類。ThreadLocalMap是ThreadLocal類的一個(gè)靜態(tài)內(nèi)部類,它實(shí)現(xiàn)了鍵值對(duì)的設(shè)置和獲取(對(duì)比Map對(duì)象來理解),每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本,它所存儲(chǔ)的值,只能被當(dāng)前線程讀取和修改。ThreadLocal類通過操作每一個(gè)線程特有的ThreadLocalMap副本,從而實(shí)現(xiàn)了變量訪問在不同線程中的隔離。因?yàn)槊總€(gè)線程的變量都是自己特有的,完全不會(huì)有并發(fā)錯(cuò)誤。還有一點(diǎn)就是,ThreadLocalMap存儲(chǔ)的鍵值對(duì)中的鍵是this對(duì)象指向的ThreadLocal對(duì)象,而值就是你所設(shè)置的對(duì)象了。

          3ThreadLocalMap

          ThreadLocalMap并不是為了解決線程安全問題,而是提供了一種將實(shí)例綁定到當(dāng)前線程的機(jī)制,類似于隔離的效果,實(shí)際上自己在方法中new出來變量也能達(dá)到類似的效果。ThreadLocalMap跟線程安全基本不搭邊,綁定上去的實(shí)例也不是多線程公用的,而是每個(gè)線程new一份,這個(gè)實(shí)例肯定不是共用的,如果共用了,那就會(huì)引發(fā)線程安全問題。ThreadLocalMap最大的用處就是用來把實(shí)例變量共享成全局變量,在程序的任何方法中都可以訪問到該實(shí)例變量而已。網(wǎng)上很多人說ThreadLocalMap是解決了線程安全問題,其實(shí)是望文生義,兩者不是同類問題。

          4設(shè)計(jì)的初衷

          ThreadLocal設(shè)計(jì)的初衷是為了解決多線程編程中的資源共享問題。提起這個(gè),大家一般會(huì)想到synchronized,synchronized采取的是“以時(shí)間換空間”的策略,本質(zhì)上是對(duì)關(guān)鍵資源上鎖,讓大家排隊(duì)操作。而ThreadLocal采取的是“以空間換時(shí)間”的思路,為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,在本線程內(nèi)部,它相當(dāng)于一個(gè)“全局變量”,可以保證本線程任何時(shí)間操縱的都是同一個(gè)對(duì)象。

          5最重要的概念

          ThreadLocal類最重要的一個(gè)概念是,其原理是通過一個(gè)ThreadLocal的靜態(tài)內(nèi)部類ThreadLocalMap實(shí)現(xiàn),但是實(shí)際中,ThreadLocal不保存ThreadLocalMap,而是有每個(gè)Thread內(nèi)部維護(hù)ThreadLocal.ThreadLocalMap threadLocals一份數(shù)據(jù)結(jié)構(gòu)。

          這里畫張圖更容易理解,假如我們有如下的代碼:

          class ThreadLocalDemo
          {
              ThreadLocal<Integer> localA = new ThreadLocal<Integer>();
              ThreadLocal<Integer> localB = new ThreadLocal<Integer>();
          }

          在多線程環(huán)境下,數(shù)據(jù)結(jié)構(gòu)應(yīng)該是如下圖所示:

          6一般步驟

          • (1)在多線程的類(如ThreadDemo類)中,創(chuàng)建一個(gè)ThreadLocal對(duì)象threadXxx,用來保存線程間需要隔離處理的對(duì)象xxx。

          • (2)在ThreadDemo類中,創(chuàng)建一個(gè)獲取要隔離訪問的數(shù)據(jù)的方法getXxx(),在方法中判斷,若ThreadLocal對(duì)象為null時(shí)候,應(yīng)該new()一個(gè)隔離訪問類型的對(duì)象,并強(qiáng)制轉(zhuǎn)換為要應(yīng)用的類型。

          • (3)在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數(shù)據(jù),這樣可以保證每個(gè)線程對(duì)應(yīng)一個(gè)數(shù)據(jù)對(duì)象,在任何時(shí)刻都操作的是這個(gè)對(duì)象。

          7ThreadLocal 與 synchronized 的對(duì)比

          • (1)ThreadLocal和synchonized都用于解決多線程并發(fā)訪問。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別。synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問。而ThreadLocal為每一個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問到的并不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。而synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享。

          • (2)synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離。

          8一句話理解ThreadLocal

          向ThreadLocal里面存東西就是向它里面的Map存東西的,然后ThreadLocal把這個(gè)Map掛到當(dāng)前的線程底下,這樣Map就只屬于這個(gè)線程了。

          -- END --

           | 更多精彩文章 -




          加我微信,交個(gè)朋友
          長(zhǎng)按/掃碼添加↑↑↑

          瀏覽 62
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  成人网站免费视频久久网 | 日韩日批视频 | 国产女人18毛片水真多成人如厕 | 国产精品久久久91 | 哪里可以免费看A片 |