java併發:線程同步機制之ThreadLocal

1.簡述ThreadLocalhtml

  ThreadLocal實例一般做爲靜態的私有的(private static)字段出如今一個類中,這個類用來關聯一個線程。ThreadLocal是一個線程級別的局部變量,下面是線程局部變量(ThreadLocal variables)的關鍵點:數組

  A、當使用ThreadLocal維護變量時,若多個線程訪問ThreadLocal實例,ThreadLocal爲每一個使用該變量的線程提供了一個獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其餘線程所對應的副本。安全

  B、從線程的角度看,目標變量就像是線程的本地變量,這也是類名中Local所要表達的意思。併發

 

2.細看ThreadLocalide

ThreadLocal<T>類很簡單,只有四個方法:函數

(1)void set(T value),該方法用來設置當前線程中變量的副本高併發

(2)public T get(),該方法是用來獲取ThreadLocal在當前線程中保存的變量副本ui

(3)public void remove(),該方法用來移除當前線程中變量的副本,目的是爲了減小內存的佔用,該方法是JDK 5.0新增的方法。須要指出的是,當線程結束之後,對應線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。this

(4)protected T initialValue(),該方法是一個protected方法,通常是用來在使用時進行重寫的,它是一個延遲加載方法,ThreadLocal中的缺省實現直接返回一個null。spa

 

3.ThreadLocal示例

簡單的使用方法以下:

package com.test;

public class ThreadMain {
    // ①經過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            return 0;
        }
    };

    // ②獲取下一個序列值
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }

    public static void main(String[] args) {
        ThreadMain sn = new ThreadMain();
        // ③ 3個線程共享sn,各自產生序列號
        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 ThreadMain sn;

        public TestClient(ThreadMain sn) {
            this.sn = sn;
        }

        public void run() {
            for (int i = 0; i < 3; i++) {
                // ④每一個線程打出3個序列值
                System.out.println("thread[" + Thread.currentThread().getName()
                        + "] --> sn[" + sn.getNextNum() + "]");
            }
        }
    }
    
}

結果以下:

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

 

另外一個案例

package com.csu.thread;

class GlobalVarManager {
    private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
        protected String initialValue() {
            return "hello";
        }
    };
    
    public static ThreadLocal<String> getglobalVars() {
        return globalVars;
    }
}

class ThreadRun implements Runnable {
    
    private ThreadLocal<String> t;
    private String str;
    
    ThreadRun(ThreadLocal<String> temp, String s) {
        this.t = temp;
        this.str = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread()+"改變前:" + t.get());
        t.set(str);
        System.out.println(Thread.currentThread()+"改變後:" + t.get());
    }
}

public class ThreadLocalTry {

    public static void main(String[] args) {
        for (int i =1; i < 5; i++) {
            new Thread(new ThreadRun(GlobalVarManager.getglobalVars(), ""+i)).start();
        }
    }
}

結果以下:

Thread[Thread-0,5,main]改變前:hello
Thread[Thread-1,5,main]改變前:hello
Thread[Thread-0,5,main]改變後:1
Thread[Thread-2,5,main]改變前:hello
Thread[Thread-1,5,main]改變後:2
Thread[Thread-2,5,main]改變後:3
Thread[Thread-3,5,main]改變前:hello
Thread[Thread-3,5,main]改變後:4

 

 上述案例也可按以下方式來實現:

package com.csu.test;

class GlobalVarManager {
    private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
        protected String initialValue() {
            return "hello";
        }
    };

    public static String getGlobalVars() {
        return globalVars.get();
    }

    public static void setGlobalVars(String str) {
        globalVars.set(str);
    }
}

class ThreadRun implements Runnable {

    private String str = null;

    public ThreadRun(String temp) {
        str = temp;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread()+"改變前:" + GlobalVarManager.getGlobalVars());
        GlobalVarManager.setGlobalVars(str);
        System.out.println(Thread.currentThread()+"改變後:" + GlobalVarManager.getGlobalVars());
    }
}

public class ThreadLocalTest {

    public static void main(String[] args) {
        for (int i = 1; i < 5; i++) {
            new Thread(new ThreadRun("" + i)).start();
        }
    }
}

結果以下:

Thread[Thread-3,5,main]改變前:hello
Thread[Thread-2,5,main]改變前:hello
Thread[Thread-1,5,main]改變前:hello
Thread[Thread-0,5,main]改變前:hello
Thread[Thread-1,5,main]改變後:2
Thread[Thread-2,5,main]改變後:3
Thread[Thread-3,5,main]改變後:4
Thread[Thread-0,5,main]改變後:1

 

4.ThreadLocal的實現機制

此部份內容暫沒有深刻研究,欲瞭解更多內容請參考https://www.cnblogs.com/dennyzhangdd/p/7978455.html

(1)get()方法源碼以下:

(2)set()方法源碼以下:

(3)remove()方法源碼以下:

(4)上述幾個函數涉及到以下兩個函數

 

從前述源碼能夠看出,ThreadLocal的get、set、remove方法都是操做當前線程,而從Thread的源碼能夠看出該類有一個ThreadLocal.ThreadLocalMap類型的變量threadLocals,該變量在初次調用ThreadLocal的set()方法時經過createMap()方法初始化

 

5.ThreadLocalMap

ThreadLocalMap的部分源碼以下:

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    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;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * 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);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

此處重點關注一下ThreadLocalMap中的幾個成員變量及方法

(1)private Entry[] table;

table是一個Entry類型的數組,該變量在ThreadLocalMap的構造函數中初始化

Entry是ThreadLocalMap的一個內部類

(2)set()方法

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

 

(3)getEntry()方法

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

 

(4)remove()方法

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

 

6.總結

ThreadLocal通常都是聲明在靜態變量中,若是不斷地建立ThreadLocal而沒有調用其remove方法,將致使內存泄露,特別是在高併發的Web容器當中。

ThreadLocal在處理線程的局部變量時比synchronized同步機制解決線程安全問題更簡單,更方便,且程序擁有更高的併發性。

相關文章
相關標籤/搜索