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

          徹底搞清楚ThreadLocal與弱引用

          共 5322字,需瀏覽 11分鐘

           ·

          2021-04-01 11:20

          眾所周知,多線程訪問(wèn)同一個(gè)競(jìng)態(tài)變量的時(shí)候容易出現(xiàn)并發(fā)問(wèn)題,特別是多個(gè)線程對(duì)一個(gè)變量進(jìn)行寫入的時(shí)候,為了保證線程安全,一般使用者在訪問(wèn)共享變量的時(shí)候需要進(jìn)行額外的同步措施才能保證線程安全性。ThreadLocal是除了加鎖這種同步方式之外的一種規(guī)避多線程訪問(wèn)出現(xiàn)線程不安全的方法,它實(shí)現(xiàn)了一種機(jī)制,這種機(jī)制可以復(fù)制一份競(jìng)態(tài)變量的副本,每個(gè)線程只訪問(wèn)一份副本,從而避免了對(duì)競(jìng)態(tài)變量的直接操作,消除了并發(fā)問(wèn)題。那么ThreadLocal的作用原理是什么呢?下面我們將用一段代碼來(lái)揭開ThreadLocal的面紗。

          一、ThreadLocal基本原理

          示例代碼如下:

          public class ThreadLocalDemo {

          public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

          public static void main(String[] args) {
          ThreadLocalDemo.threadLocal.set("hello world main");
          System.out.println("創(chuàng)建新線程前,主線程" + Thread.currentThread().getName() + "的threadlocal字符值為:" + ThreadLocalDemo.threadLocal.get());

          try {
          Thread thread = new Thread() {
          @Override
          public void run() {
          ThreadLocalDemo.threadLocal.set("new thread");
          System.out.println("新線程" + Thread.currentThread().getName() + "的threadlocal字符值為:" + ThreadLocalDemo.threadLocal.get());
          }
          };
          thread.start();
          thread.join();
          } catch (Exception e) {
          System.out.println(e);
          }
          System.out.println("創(chuàng)建新線程后,主線程" + Thread.currentThread().getName() + "的threadlocal字符值為:" + ThreadLocalDemo.threadLocal.get());

          }
          }

          代碼的邏輯很簡(jiǎn)單:在主類中定義了一個(gè)靜態(tài)變量threadLocal,在主線程中先設(shè)置這個(gè)變量的字符值為"hello world main",隨后在主線程中創(chuàng)建一個(gè)新線程,并在新線程的run方法中修改threadLocal的字符值為“new thread”,然后主線程再把threadlocal的字符值打印一次。為了確保新線程一定會(huì)在主線程第二次打印前打印threadlocal的值,這里采用join方法,讓新線程強(qiáng)行“加塞”,阻塞主線程,直到新線程執(zhí)行完run方法后,主線程才解除阻塞,繼續(xù)打印。執(zhí)行結(jié)果如下:

          從結(jié)果上來(lái)看,新線程對(duì)threadlocal字符值的修改,并沒(méi)有影響到主線程的threadlocal的字符值的變化,即使threadlocal的類型是static的。這說(shuō)明,新線程所修改的threadlocal是一份主線程的threadlocal的副本,那么這一點(diǎn)是怎么實(shí)現(xiàn)的呢?下面我們就一行行分析源代碼來(lái)了解,首先我們先看main方法中的第一行代碼:

          ThreadLocalDemo.threadLocal.set("hello world main");

          我們點(diǎn)進(jìn)這個(gè)set方法,相應(yīng)源碼如下:

          /**
          * 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) {
          Thread t = Thread.currentThread();
          ThreadLocalMap map = getMap(t);
          if (map != null)
          map.set(this, value);
          else
          createMap(t, value);
          }

          這里邊有一個(gè)ThreadLocalMap的類,并且通過(guò)一個(gè)叫g(shù)etMap(t)的方法來(lái)獲取這個(gè)類的一個(gè)實(shí)例,隨后把threadLocal變量的地址作為key,以字符值為value存放在這個(gè)map中。如果map為空的話,會(huì)調(diào)用createMap(t,value)來(lái)創(chuàng)建一個(gè)map,我們點(diǎn)進(jìn)createMap方法,代碼如下:

           /**
          * Create the map associated with a ThreadLocal. Overridden in
          * InheritableThreadLocal.
          *
          * @param t the current thread
          * @param firstValue value for the initial entry of the map
          */
          void createMap(Thread t, T firstValue) {
          t.threadLocals = new ThreadLocalMap(this, firstValue);
          }

          threadLocals是Thread類的一個(gè)靜態(tài)成員變量,它的類型是Thread的一個(gè)靜態(tài)內(nèi)部類ThreadLocalMap,如下所示:

              /* ThreadLocal values pertaining to this thread. This map is maintained
          * by the ThreadLocal class. */
          ThreadLocal.ThreadLocalMap threadLocals = null;

          我們用圖來(lái)總結(jié)一下上面的步驟,程序啟動(dòng)時(shí),執(zhí)行代碼:

          public static ThreadLocal<String> threadLocal =  new ThreadLocal<String>();

          此時(shí),整個(gè)棧內(nèi)存和堆內(nèi)存的情況如下圖所示:

          然后,在main方法中執(zhí)行:

                  ThreadLocalDemo.threadLocal.set("hello world main");

          該過(guò)程創(chuàng)建新的ThreadLocalMap實(shí)例,它的key指向ThreadLocal對(duì)象,value為“hello world main”并且這個(gè)key是個(gè)弱引用(弱引用是什么以及這里為什么使用弱引用,后面會(huì)提),如下圖所示:

          隨后,main方法中創(chuàng)建Thread,并在Thread方法中又調(diào)用了

          ThreadLocalDemo.threadLocal.set("new thread");

          因此,堆內(nèi)存中將創(chuàng)建兩個(gè)對(duì)象,一個(gè)是Thread對(duì)象,代表新線程;一個(gè)是Thread的ThreadLoaclMap的實(shí)例,如下圖所示:

          總結(jié):每在一個(gè)新線程中調(diào)用一次threadLocal.set("xxx")方法,就會(huì)在堆內(nèi)存中創(chuàng)建一個(gè)新的ThreadLocalMap實(shí)例,這個(gè)實(shí)例通過(guò)Entry的方式保存key和value,value是不同的,而key都指向同一個(gè)ThreadLocal對(duì)象。

          二、為什么使用弱引用

          我們知道java的引用分為強(qiáng)、軟、弱、虛四種類型,其他類型因篇幅有限,暫且不表。只說(shuō)說(shuō)弱引用,弱引用的定義是:如果一個(gè)對(duì)象僅被一個(gè)弱引用指向,那么當(dāng)下一次GC到來(lái)時(shí),這個(gè)對(duì)象一定會(huì)被垃圾回收器回收掉。觀察ThreadLocalMap的源碼:

                  /**
          * The entries in this hash map extend WeakReference, using
          * its main ref field as the key (which is always a
          * ThreadLocal object). Note that null keys (i.e. entry.get()
          * == null) mean that the key is no longer referenced, so the
          * entry can be expunged from table. Such entries are referred to
          * as "stale entries" in the code that follows.
          */
          static class Entry extends WeakReference<ThreadLocal<?>> {
          /** The value associated with this ThreadLocal. */
          Object value;

          Entry(ThreadLocal<?> k, Object v) {
          super(k);
          value = v;
          }
          }

          我們觀察到ThreadLocalMap的key繼承了弱引用,這是為什么呢?光結(jié)合定義來(lái)體會(huì)肯定無(wú)法深入體會(huì),讓我們結(jié)合圖來(lái)分析一下。還是上面那張圖,假設(shè)兩條虛線不是弱引用,而是強(qiáng)引用,如下圖紅線所示:

          此時(shí),假設(shè)我們?cè)谥骶€程或者新線程中添加一行代碼 :

          ThreadLocalDemo.threadLocal = null;

          即我們主動(dòng)釋放掉對(duì)ThreadLocalDemo.threadLocal 在兩個(gè)線程中的引用,結(jié)果如下圖所示:

          我們可以看到,雖然兩個(gè)線程都主動(dòng)釋放掉了對(duì)ThreadLocal對(duì)象的引用,但是,從主線程thread引用->ThreadLocal對(duì)象,依然存在這一條可達(dá)路徑。眾所周知,現(xiàn)今主流JVM判斷一個(gè)對(duì)象是否可回收的算法通常為可達(dá)路徑算法,而不是引用計(jì)數(shù)法。可達(dá)路徑算法以GCROOT出發(fā),如果存在一條通向某個(gè)對(duì)象的強(qiáng)引用通路,那么這個(gè)對(duì)象是永遠(yuǎn)不會(huì)回收掉的(即便發(fā)生OOM也不會(huì)回收)。thread的引用是主線程的一個(gè)本地變量,根據(jù)GCROOT算法,thread的引用是可以作為一個(gè)GCROOT的,那么現(xiàn)狀就是:我們顯式地釋放掉了threadLocal的引用(ThreadLocalDemo.threadLocal = null;),因?yàn)槲覀兇_認(rèn)后續(xù)我們不會(huì)使用到它了,但是,由于存在GCROOT的一條可達(dá)通路,程序并沒(méi)有像我們希望的那樣立刻釋放掉ThreadLocal對(duì)象,直到我們所有的線程都釋放掉了,即程序結(jié)束,ThreadLocal對(duì)象才會(huì)被真正的釋放掉,這無(wú)疑就是內(nèi)存泄露。為了解決這個(gè)問(wèn)題,我們把圖中的紅線換成弱引用,如下圖所示

          本文來(lái)自知乎--冒藍(lán)火加特林
          瀏覽 38
          點(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>
                  亚洲欧美v在线视频 | 91精品人妻无码 | 高清无码在线免费 | 黄色网页在线视频 | 黄色视频网站国产 |