1. 程式人生 > 其它 >mac怎麼設定開機自啟動項,mac選擇開機啟動項

mac怎麼設定開機自啟動項,mac選擇開機啟動項

ThreadLocal 詳解

ThreadLocal簡介

ThreadLocal叫做執行緒變數,意思是ThreadLocal中填充的變數屬於當前執行緒,該變數對其他執行緒而言是隔離的,也就是說該變數是當前執行緒獨有的變數。ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數。
ThreadLoal 變數,執行緒區域性變數,同一個 ThreadLocal 所包含的物件,在不同的 Thread 中有不同的副本。這裡有幾點需要注意:

  • 因為每個 Thread 內有自己的例項副本,且該副本只能由當前 Thread 使用。這是也是 ThreadLocal 命名的由來。
  • 既然每個 Thread 有自己的例項副本,且其它 Thread 不可訪問,那就不存在多執行緒間共享的問題。

ThreadLocal 提供了執行緒本地的例項。它與普通變數的區別在於,每個使用該變數的執行緒都會初始化一個完全獨立的例項副本。ThreadLocal 變數通常被private static修飾。當一個執行緒結束時,它所使用的所有 ThreadLocal 相對的例項副本都可被回收。
總的來說,ThreadLocal 適用於每個執行緒需要自己獨立的例項且該例項需要在多個方法中被使用,也即變數線上程間隔離而在方法或類間共享的場景

ThreadLocal的簡單使用

public class ThreadLocaDemo {
 
    private static ThreadLocal<String> localVar = new ThreadLocal<String>();
 
    static void print(String str) {
        //列印當前執行緒中本地記憶體中本地變數的值
        System.out.println(str + " :" + localVar.get());
        //清除本地記憶體中的本地變數
        localVar.remove();
    }
    public static void main(String[] args) throws InterruptedException {
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_A");
                print("A");
                //列印本地變數
                System.out.println("after remove : " + localVar.get());
               
            }
        },"A").start();
 
        Thread.sleep(1000);
 
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_B");
                print("B");
                System.out.println("after remove : " + localVar.get());
              
            }
        },"B").start();
    }
}
 
A :local_A
after remove : null
B :local_B
after remove : null

從這個示例中我們可以看到,兩個執行緒分表獲取了自己執行緒存放的變數,他們之間變數的獲取並不會錯亂。

ThreadLocal的原理

ThreadLocal的set()方法

 public void set(T value) {
        //1、獲取當前執行緒
        Thread t = Thread.currentThread();
        //2、獲取執行緒中的屬性 threadLocalMap ,如果threadLocalMap 不為空,
        //則直接更新要儲存的變數值,否則建立threadLocalMap,並賦值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 並賦值
            createMap(t, value);
    }

從上面的程式碼可以看出,ThreadLocal set賦值的時候首先會獲取當前執行緒thread,並獲取thread執行緒中的ThreadLocalMap屬性。如果map屬性不為空,則直接更新value值,如果map為空,則例項化threadLocalMap,並將value值初始化。
那麼ThreadLocalMap又是什麼呢,還有createMap又是怎麼做的,我們繼續往下看。

 static class 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是ThreadLocal的內部靜態類,而它的構成主要是用Entry來儲存資料 ,而且還是繼承的弱引用。在Entry內部使用ThreadLocal作為key,使用我們設定的value作為value.

//這個是threadlocal 的內部方法
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //ThreadLocalMap 構造方法
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);
        }

ThreadLocal的get方法

public T get() {
    //1、獲取當前執行緒
    Thread t = Thread.currentThread();
    //2、獲取當前執行緒的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    //3、如果map資料不為空,
    if (map != null) {
        //3.1、獲取threalLocalMap中儲存的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果是資料為null,則初始化,初始化的結果,TheralLocalMap中存放key值為threadLocal,值為null
    return setInitialValue();
}
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;
}

ThreadLocal的remove方法

public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

remove方法,直接將ThrealLocal 對應的值從當前相差Thread中的ThreadLocalMap中刪除。為什麼要刪除,這涉及到記憶體洩露的問題。

實際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點是,如果這個物件只存在弱引用,那麼在下一次垃圾回收的時候必然會被清理掉。

所以如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,這樣一來 ThreadLocalMap中使用這個 ThreadLocal 的 key 也會被清理掉。但是,value 是強引用,不會被清理,這樣一來就會出現 key 為 null 的 value。

ThreadLocal其實是與執行緒繫結的一個變數,如此就會出現一個問題:如果沒有將ThreadLocal內的變數刪除(remove)或替換,它的生命週期將會與執行緒共存。通常執行緒池中對執行緒管理都是採用執行緒複用的方法,線上程池中執行緒很難結束甚至於永遠不會結束,這將意味著執行緒持續的時間將不可預測,甚至與JVM的生命週期一致。舉個例字,如果ThreadLocal中直接或間接包裝了集合類或複雜物件,每次在同一個ThreadLocal中取出物件後,再對內容做操作,那麼內部的集合類和複雜物件所佔用的空間可能會開始持續膨脹。

記憶體洩漏問題

Entry將ThreadLocal作為Key,值作為value儲存,它繼承自WeakReference,注意建構函式裡的第一行程式碼super(k),這意味著ThreadLocal物件是一個「弱引用」

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

主要兩個原因
1 . 沒有手動刪除這個 Entry
2 . CurrentThread 當前執行緒依然執行

    第一點很好理解,只要在使用完下 ThreadLocal ,呼叫其 remove 方法刪除對應的 Entry ,就能避免記憶體洩漏。
    第二點稍微複雜一點,由於ThreadLocalMap 是 Thread 的一個屬性,被當前執行緒所引用,所以ThreadLocalMap的生命週期跟 Thread 一樣長。如果threadlocal變數被回收,那麼當前執行緒的threadlocal 變數副本指向的就是key=null, 也即entry(null,value),那這個entry對應的value永遠無法訪問到。實際私用ThreadLocal場景都是採用執行緒池,而執行緒池中的執行緒都是複用的,這樣就可能導致非常多的entry(null,value)出現,從而導致記憶體洩露。

綜上, ThreadLocal 記憶體洩漏的根源是:
由於ThreadLocalMap 的生命週期跟 Thread 一樣長,對於重複利用的執行緒來說,如果沒有手動刪除(remove()方法)對應 key 就會導致entry(null,value)的物件越來越多,從而導致記憶體洩漏.
為了避免這種情況,我們可以在使用完ThreadLocal後,手動呼叫remove方法,以避免出現記憶體洩漏。