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

深入淺出解析Java ThreadLocal原理

 更新時間:2021年11月11日 10:58:17   作者:ATFWUS  
ThreadLocal是JDK包提供的,它提供線程本地變量,如果創(chuàng)建一樂ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內(nèi)存中的變量,從而規(guī)避了線程安全問題,感興趣的朋友快來看看吧

分享一下最近看的ThreadLocal的源碼的一些體會。

1.了解ThreadLocal

簡介

  • ThreadLocal是JDK中java.lang包下提供的類。
  • ThreadLocal是線程安全的,并且沒有使用到鎖。
  • 常用來存放線程獨有變量,解決參數(shù)傳遞問題。
  • 當我們創(chuàng)建一個ThreadLocal包裝的變量后,每個訪問這個變量的線程會在自己的線程空間創(chuàng)建這個變量的一個副本,在每次操作這個變量的時候,都是在自己的線程空間內(nèi)操作,解決了線程安全問題。

在這里插入圖片描述

使用

  • (是線程安全的) 在這個demo中,localStr是共享的,隨后在每個線程中給localStr設(shè)置值為自己線程的名字,然后再將當前線程的日志輸出。
  • sleep5毫秒是為了體現(xiàn)出是否存在線程安全問題。
  • 從運行結(jié)果可以看到,是不存在線程安全問題的:
/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/8 21:23
 * @description
 */
@Slf4j
public class ThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();

    public static void main(String[] args) {
        List<Thread> list = new LinkedList<>();
        for(int i = 0; i < 1000; i++){
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    localStr.set(Thread.currentThread().getName() + " localStr");
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(localStr.get());
                }
            }, "t" + String.valueOf(i));
            list.add(t);
        }
        for (Thread t : list) {
            t.start();
        }

    }
}

在這里插入圖片描述

而對于普通變量來說,很明顯是存在線程安全問題的:

/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/8 21:23
 * @description
 */
@Slf4j
public class ThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();
    static String shareStr;

    public static void main(String[] args) {
        List<Thread> list = new LinkedList<>();
        for(int i = 0; i < 1000; i++){
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    shareStr = Thread.currentThread().getName() + "  shareStr";
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(shareStr);
                }
            }, "t" + String.valueOf(i));
            list.add(t);
        }
        for (Thread t : list) {
            t.start();
        }

    }
}

在這里插入圖片描述

2.源碼解析 – 探究實現(xiàn)思路

threadLocals變量與ThreadLocalMap

  • 每個線程的本地變量并不存放于ThreadLocal對象中,而是存在調(diào)用線程的threadLocals變量中。因為是線程對象的成員變量,所以生命周期等同于線程的生命周期。

在這里插入圖片描述

  • 而threadLocals是ThreadLocalMap類的實例。
  • ThreadLocalMap實際上是一個類似HashMap的實現(xiàn),是ThreadLocal的靜態(tài)內(nèi)部類。
  • 看下Doug Lea寫的注釋: ThreadLocalMap是一個定制的hash map,僅適用于維護線程本地值。在ThreadLocal類之外沒有暴露任何的操作。這個類是私有的,允許在類線程中聲明字段。為了處理非常大并長期存在(對象)的用法,哈希表的entries使用weakReference作為鍵。但是,由于沒有使用引用隊列,因此只有當表開始耗盡空間時,才能保證刪除過時的entries。

在這里插入圖片描述

  • 暫不探究ThreadLocalMap的內(nèi)部實現(xiàn)細節(jié),暫時只需要知道實現(xiàn)了一個hash map,并且Entry的key是弱引用即可,具體的set() get() remove() 方法在下文中會有。

set(T value) 方法

  • 進入set(T value) 方法后,先嘗試獲取map,如果獲取到了map,直接設(shè)置值,否則新建一個map。
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

get() 方法

  1. 進入get()方法后,首先獲取當前線程,然后進入getMap(Thread t)中獲取ThreadLocalMap對象,直接返回t.threadLocals。
  2. 如果map不為空,直接返回map中當前ThreadLocal作為鍵對應的值。
  3. 如果map為空,需要先進行初始化。調(diào)用setInitialValue()方法進行初始化。
  • setInitialValue()中先獲取一個初始值,默認為null。
  • 如果map存在當前線程中,直接設(shè)置初始值。
  • 如果map不存在當前線程中,需要先創(chuàng)建一個map。
  • createMap(Thread t, T firstValue)中就是new了一個ThreadLocalMap對象,并且初始化了一個entry對。
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    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;
    }
    protected T initialValue() {
        return null;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

remove() 方法

  • remove() 方法中,先判斷map是否存在,不存在直接將map中this作為鍵的entry刪掉。
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

實現(xiàn)思路總結(jié)

  • ThreadLocal搭配線程的threadLocals變量實現(xiàn),當調(diào)用set(T value) 和 get() 方法時,如果線程中的threadLocals仍然為null,會為其初始化。
  • ThreadLocal對象往threadLocals存儲具體變量時,key是ThreadLocal對象的自身引用,value是真正的變量,且key是弱引用。

在這里插入圖片描述

3.InheritableThreadLocal與繼承性

InheritableThreadLocal英語翻譯一下就是可繼承的ThreadLocal,讓我們看下它和ThreadLocal的繼承性體現(xiàn)在哪。

這里的繼承性指的是:子線程是否能訪問父線程的變量。

ThreadLocal的不可繼承性

threadLocals是當前線程的成員變量,在子線程中不可見

/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/9 14:29
 * @description
 */
@Slf4j
public class InheritableThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();

    public static void main(String[] args) {
        localStr.set("main線程為其設(shè)置的值");
        new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("訪問localStr : " + localStr.get());
            }
        }).start();
        System.out.println(localStr.get());
    }
}

在這里插入圖片描述

InheritableThreadLocal實現(xiàn)繼承性的源碼剖析

看一下InheritableThreadLocal的源碼:

在這里插入圖片描述

源碼非常簡短,下面簡單分析一下:

  • InheritableThreadLocal類繼承自ThreadLocal類,重寫了childValue(T parentValue)、getMap()、createMap(Thread t, T firstValue) 三個方法。
  • createMap(Thread t, T firstValue)會在初始化的時候調(diào)用,重寫createMap(Thread t, T firstValue) 意味著,InheritableThreadLocal的實例使用的是線程對象中的inheritableThreadLocals,而不再是原來的threadLocals。
  • getMap() 方法也是確保使用的是inheritableThreadLocals。
  • childValue(T parentValue) 方法中,直接返回了parentValue,這個方法會在ThreadLocal的構(gòu)造方法中被調(diào)用,為了弄清這個意圖,我們有必要看看Thread類初始化方法的源碼。

從Thread的構(gòu)造方法看,發(fā)現(xiàn)所有的構(gòu)造方法都會調(diào)用init()方法進行初始化,init()方法有兩個重載形式。

在這里插入圖片描述

我們進入?yún)?shù)較多的init方法查看一下:

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
				// 新線程還未創(chuàng)建出來,當前線程就是即將要創(chuàng)建線程的父線程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        // 如果父線程的inheritThreadLocals 不為空
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        		 // 設(shè)置子線程中的inheritableThreadLocals設(shè)置為父線程的inheritableThreadLocals
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

我們重點看一下和inheritThreadLocals相關(guān)的地方(含注釋的地方)

  • 在進入init方法后,先獲取了父線程,然后再下面判斷了父線程的inheritThreadLocals 是否為空,不為空就調(diào)用ThreadLocal.createInheritedMap方法,參數(shù)就是父線程的inheritThreadLocals 。

再看下ThreadLocal.createInheritedMap方法:

  • 調(diào)用了自身的構(gòu)造方法,將parentMap傳入。
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

看下這個構(gòu)造方法:

  • 發(fā)現(xiàn)主要是用parentMap的所有entry初始化當前的map。
  • 在注釋處,調(diào)用了inheritThreadLocals重寫的childValue方法,而重寫后,直接返回的是parentValue,也就是將父線程的inheritThreadLocal里面的entry完整的復制到了子線程中。
        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) {
                        // 調(diào)用inheritThreadLocals重寫的childValue方法
                        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++;
                    }
                }
            }
        }

如何理解這個繼承性

通過上面的源碼分析,可以發(fā)現(xiàn),InheritableThreadLocal的繼承性主要體現(xiàn)在:創(chuàng)建子線程時,會將父線程的inheritThreadLocals里面所有entry拷貝一份給子進程。

那么當子進程被創(chuàng)建出來之后,父進程又修改了inheritThreadLocals里面的值,這個操作是否對子線程可見,通過上面的源碼可知,這個操作明顯是不可見的,下面有個demo可以證實。

  • sleep操作是為了控制兩個線程的執(zhí)行流程。
/**
 * @author ATFWUS
 * @version 1.0
 * @date 2021/11/9 14:29
 * @description
 */
@Slf4j
public class InheritableThreadLocalTest {

    static ThreadLocal<String> localStr = new ThreadLocal<>();
    static InheritableThreadLocal<String> inheritableLocalStr = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        inheritableLocalStr.set("main線程第一次為inheritableLocalStr設(shè)置的值");
        new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("子線程第一次訪問inheritableLocalStr : " + inheritableLocalStr.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("子線程第二次訪問inheritableLocalStr : " + inheritableLocalStr.get());
            }
        }).start();
        Thread.sleep(500);
        inheritableLocalStr.set("main線程第二次為inheritableLocalStr設(shè)置的值");
        log.debug("main線程第二次為inheritableLocalStr賦值");
        Thread.sleep(1000);
    }
}

看下輸出:

在這里插入圖片描述

可以發(fā)現(xiàn),子線程創(chuàng)建出來后,對父線程中inheritThreadLocals的修改操作,對子線程不可見。

總結(jié)

  • ThreadLocal不可繼承,threadLocals是當前線程的成員變量,在子線程中不可見。
  • InheritableThreadLocal可繼承,原理是:在新建子線程的時候,將父線程中inheritThreadLocals所有的entry拷貝給了子線程。
  • 子線程創(chuàng)建出來后,對父線程中inheritThreadLocals的修改操作,對子線程不可見。

4.存在的內(nèi)存泄露問題

要充分理解ThreadLocal中存在的內(nèi)存泄露問題,需要有以下JVM對內(nèi)存管理的前置知識(這里篇幅問題就不補充了):

  • 什么是內(nèi)存泄露?
  • 什么是強引用?
  • 什么是弱引用?
  • 何時GC?
  • 強引用和弱引用GC時的區(qū)別?

在分析上述ThreadLocalMap源碼的時候,注意到有一個小細節(jié),ThreadLocalMap的Entry繼承了WeakReference<ThreadLocal<?>>,也就是說Entry的key是一個對ThreadLocal<?>的弱引用。問題來了,為什么這里要使用弱引用呢?

在這里插入圖片描述

使用強引用會如何?

現(xiàn)在假設(shè)Entry的key是一個對ThreadLocal的強引用,當ThreadLocal對象使用完后,外部的強引用不存在,但是因為當前線程對象中的threadLocals還持有ThreadLocal的強引用,而threadLocals的生命周期是和線程一致的,這個時候,如果沒有手動刪除,整個Entry就發(fā)生了內(nèi)存泄露。

使用弱引用會如何?

現(xiàn)在假設(shè)Entry的key是一個對ThreadLocal的弱引用,當ThreadLocal對象使用完后,外部的強引用不存在,此時ThreadLocal對象只存在Entry中key對它的弱引用,在下次GC的時候,這個ThreadLocal對象就會被回收,導致key為null,此時value的強引用還存在,但是value已經(jīng)不會被使用了,如果沒有手動刪除,那么這個Entry中的key就會發(fā)生內(nèi)存泄露。

使用弱引用還有一些好處,那就是,當key為null時, ThreadLocalMap中最多存在一個key為null,并且當調(diào)用set(),get(),remove()這些方法的時候,是會清除掉key為null的entry的。

set()、get()、remove() 方法中相關(guān)實現(xiàn)

  • 從下可以發(fā)現(xiàn),set方法首先會進入一個循環(huán)。
  • 在這個循環(huán)中,會遍歷整個Entry數(shù)組。直到遇到一個空的entry,退出循環(huán)。
  • 當遇到已存在的key'時,會直接替換value,然后返回。
  • 當遇到key為空的entry的時候,會直接將當前的entry存在這個過時的entry中,然后返回。

通過這個方法的源碼可以看出,key為null的那個entry實際上遲早會被替換成新的entry。

        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;
                }
							 // 發(fā)現(xiàn)key為空
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

同理,可以看到在get方法中也存在:

        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);
        }
         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;
                // 替換過時的entry
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

remove() 方法中也是一樣:

        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)]) {
                // 清除過時的key
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

總結(jié)

  • ThreadLocal如果對ThreadLocalMap的key使用強引用,那么會存在整個entry發(fā)生內(nèi)存泄露的問題,如果不手動清除,那么這個不被使用的entry會一直存在。
  • ThreadLocal如果對ThreadLocalMap的key使用弱引用,那么可能會存在一個entry的value發(fā)生內(nèi)存泄露,但是在調(diào)用set(),get(),remove() 方法時,key為null的entry會被清除掉。
  • 發(fā)生內(nèi)存泄露最根本的原因是:threadLocals的生命周期是和線程一致的。
  • 每次使用完ThreadLocal對象后,必須調(diào)用它的remove()方法清除數(shù)據(jù)。

5.ThreadLocal應用

ThreadLocal把數(shù)據(jù)存放到線程本地,解決了線程安全問題,沒有使用鎖,直接訪問線程本地變量,效率較高(空間換時間。)
同時threadLocals的生命周期是和線程一致的,可以解決很多參數(shù)傳遞問題。

  • Session管理(Mabaties使用ThreadLocal存儲session),數(shù)據(jù)庫連接。
  • 如果需要跟蹤請求的整個流程,可以使用ThreadLocal來傳遞參數(shù)。

ATFWUS 2021-11-11

以上就是深入淺出解析Java ThreadLocal原理的詳細內(nèi)容,更多關(guān)于Java ThreadLocal原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java的反射機制之類加載詳解

    Java的反射機制之類加載詳解

    這篇文章主要介紹了Java的反射機制之類加載詳解,反射機制是java實現(xiàn)動態(tài)語言的關(guān)鍵,也就是通過反射實現(xiàn)類動態(tài)加載,靜態(tài)加載是指在編譯時期確定要加載的類的類型,即通過class關(guān)鍵字和類名來獲取對應類的類型,需要的朋友可以參考下
    2023-09-09
  • 23種設(shè)計模式(11)java策略模式

    23種設(shè)計模式(11)java策略模式

    這篇文章主要為大家詳細介紹了23種設(shè)計模式之java策略模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • SpringBoot整合MongoDB實現(xiàn)文件上傳下載刪除

    SpringBoot整合MongoDB實現(xiàn)文件上傳下載刪除

    這篇文章主要介紹了SpringBoot整合MongoDB實現(xiàn)文件上傳下載刪除的方法,幫助大家更好的理解和學習使用SpringBoot框架,感興趣的朋友可以了解下
    2021-05-05
  • SpringBoot中如何啟動Tomcat流程

    SpringBoot中如何啟動Tomcat流程

    這篇文章主要介紹了SpringBoot中如何啟動Tomcat流程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • Java中自動生成構(gòu)造方法詳解

    Java中自動生成構(gòu)造方法詳解

    這篇文章主要介紹了Java中自動生成構(gòu)造方法詳解的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • Java 十大排序算法之冒泡排序刨析

    Java 十大排序算法之冒泡排序刨析

    冒泡排序是一種簡單的排序算法,它也是一種穩(wěn)定排序算法。其實現(xiàn)原理是重復掃描待排序序列,并比較每一對相鄰的元素,當該對元素順序不正確時進行交換。一直重復這個過程,直到?jīng)]有任何兩個相鄰元素可以交換,就表明完成了排序
    2021-11-11
  • SpringBoot如何手寫一個starter并使用這個starter詳解

    SpringBoot如何手寫一個starter并使用這個starter詳解

    starter是SpringBoot中的一個新發(fā)明,它有效的降低了項目開發(fā)過程的復雜程度,對于簡化開發(fā)操作有著非常好的效果,下面這篇文章主要給大家介紹了關(guān)于SpringBoot如何手寫一個starter并使用這個starter的相關(guān)資料,需要的朋友可以參考下
    2022-12-12
  • Java 中實現(xiàn)隨機無重復數(shù)字的方法

    Java 中實現(xiàn)隨機無重復數(shù)字的方法

    為了更好地理解這個題意,我們先來看下具體內(nèi)容:生成一個1-100 的隨機數(shù)組,但數(shù)組中的數(shù)字不能重復,即位置是隨機的,但數(shù)組元素不能重復
    2013-03-03
  • Java反射機制的實現(xiàn)詳解

    Java反射機制的實現(xiàn)詳解

    反射主要解決動態(tài)編程,即使用反射時,所有的對象生成是動態(tài)的,因此調(diào)用的方法也是動態(tài)的.反射可以簡化開發(fā),但是代碼的可讀性很低
    2013-05-05
  • 手把手搭建Java共享網(wǎng)盤的方法步驟

    手把手搭建Java共享網(wǎng)盤的方法步驟

    這篇文章主要介紹了手把手搭建Java共享網(wǎng)盤,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12

最新評論