最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中的ThreadLocal線程變量詳解

 更新時間:2024年01月17日 09:41:03   作者:java-zh  
這篇文章主要介紹了Java中的ThreadLocal線程變量詳解,ThreadLocal叫做線程變量,意思是在ThreadLocal中填充的變量屬于當(dāng)前線程,該變量對其他線程而言是隔離的,它是用來提供線程內(nèi)部的局部變量,需要的朋友可以參考下

ThreadLocal 簡介

介紹

ThreadLocal叫做線程變量,意思是在ThreadLocal中填充的變量屬于當(dāng)前線程,該變量對其他線程而言是隔離的,它是用來提供線程內(nèi)部的局部變量。這種變量在多線程環(huán)境下訪問時能保證各個線程的變量相對獨(dú)立于其他線程內(nèi)的變量。

用一句話來概括:提供線程局部變量,一個局部變量,在多線程中,分別有獨(dú)立的值。

特點(diǎn)

  • 每個線程都有自己的線程局部變量,且只能訪問自己的,不能訪問其他線程的
  • ThreadLocal變量通常被private static修飾,當(dāng)一個線程銷毀(結(jié)束)時,其所有的ThreadLocal變量都會被回收

圖形解析

  1. 每個Thread線程內(nèi)部都有一個Map(ThreadLocalMap)
  2.  ThreadLocalMap里面存儲ThreadLocal對象(key)和線程的變量值(value)
  3. Thread內(nèi)部的ThreadLocalMap是由ThreadLocalMap維護(hù)的,由ThreadLocal負(fù)責(zé)向ThreadLocalMap獲取和設(shè)置線程值
  4. 對于不同的線程,每次獲取值時,別的線程并不能獲取到當(dāng)前線程值,擁有線程隔離,互不干擾

ThreadLocal實(shí)現(xiàn)線程隔離基本原理

  1. ThreadLocal本身不存放數(shù)據(jù),使用線程Thread中的threadLocals屬性,(ThreadLocal是Thread中屬性threadLocals的管理者),threadLocals屬性對應(yīng)在ThreadLocal中定義的ThreadLocalMap對象。
  2. 在調(diào)用ThreadLocal的set()方法時,會將自身的引用this作為key,用戶傳入的值作為value存入ThreadLocalMap中。這樣每個線程的讀寫操作都是基于線程本身的一個私有值,線程之間的數(shù)據(jù)是相互隔離的,互不影響。

ThreadLoca常用的API方法

  • get():返回當(dāng)前線程的ThreadLoca變量的值,如果當(dāng)前線程沒有該 變量就初始化為null返回,如果ThreadLocals屬性沒有就被創(chuàng)建,就新建一個ThreadLocalMap,并創(chuàng)建一個ThreadLocal變量存入map中 
  • set(T value):將當(dāng)前線程的ThreadLocal變量副本中的值指定為value
  • remove():移除當(dāng)前線程的ThreadLocal變量

ThreadLocal源碼解析

get()

public T get() {
        // 獲取到當(dāng)前線程
        Thread t = Thread.currentThread();
        // 從當(dāng)前線程t中獲取thredLocals屬性
        ThreadLocalMap map = getMap(t);
        //如果不為null,就說明已經(jīng)初始化過,存在值
        if (map != null) {
            // 獲取當(dāng)前ThreadLocal對象為key值(如果不存在,就創(chuàng)建一個ThreadLocal變量)
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                //如果值不為null,就直接返回
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果當(dāng)前線程的threadlocals沒有被創(chuàng)建,就調(diào)用setInitialValue()創(chuàng)建
        return setInitialValue();
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
private T setInitialValue() {
        // value賦值為初始化方法值,默認(rèn)為null
        T value = initialValue();
        // 獲取到當(dāng)前線程
        Thread t = Thread.currentThread();
        // 獲取當(dāng)前線程t的threadlocals屬性,也就是map集合
        ThreadLocalMap map = getMap(t);
        // 如果map不為null,進(jìn)行賦值
        if (map != null)
            map.set(this, value);
        else
            //map為null,創(chuàng)建當(dāng)前線程t的map并賦值
            createMap(t, value);
        return value;
    }
 

set()

public void set(T value) {
        // 獲取到當(dāng)前線程
        Thread t = Thread.currentThread();
        // 獲取到當(dāng)前線程的threadLocals值,即map值
        ThreadLocalMap map = getMap(t);
        // 如果map不等于null,添加值
        if (map != null)
            map.set(this, value);
        // 如果等于null,創(chuàng)建當(dāng)前線程t的map,并添加值
        else
            createMap(t, value);
    }

 remove()

public void remove() {
        // 獲取當(dāng)前線程的threadLocals值,即map集合
         ThreadLocalMap m = getMap(Thread.currentThread());
         // map不為null就將ThreadLocal變量(this)移除
         if (m != null)
             m.remove(this);
     }
}

適用場景

  1. 每個線程需要有自己的獨(dú)立實(shí)例
  2. 實(shí)例需要在多個方法中共享,但不希望被多線程共享

場景舉例:用來存儲用戶信息,數(shù)據(jù)庫連接,數(shù)據(jù)夸層傳遞(由service1傳到service2),解決線程安全問題等等

存在問題

ThreadLoal可能會造成內(nèi)存泄漏,主要原因是線程復(fù)用。

由于ThreadLocal對象是弱引用,如果沒有外部的強(qiáng)引用指向,會導(dǎo)致ThreadLoca對象被回收,Entry中的key變成空,此時如果value沒有外部引用指向,value就永遠(yuǎn)訪問不到了,(按理應(yīng)該被GC回收,但是由于Entry對象table數(shù)組還在強(qiáng)引用value)此時會發(fā)生內(nèi)存泄漏,value成為無法訪問但又永遠(yuǎn)無法回收的對象(除非線程被銷毀,但是處于系統(tǒng)性能考慮,線程不宜頻繁的創(chuàng)建和銷毀,經(jīng)常適用線程池,這樣線程的生命周期變大,內(nèi)存泄漏的影響也就會變大)

總結(jié):threadLocals對象中的Entry對象不再使用后,如果沒有及時清除Entry對象,而程序自身也無法通過垃圾回收機(jī)制自動清除,那么就可能會造成內(nèi)存泄漏。

解決內(nèi)存泄漏方案

  • 使用完ThreadLocal變量以后,應(yīng)該盡快調(diào)用remove方法,將其從ThreadLocalMap中清除,以便讓垃圾回收器回收他們。
  • 將ThreadLocal變量定義成private static類型,并且咋使用完以后手動清除,以避免線程重用時引起的內(nèi)存泄漏問題
  • 不要在線程池中使用ThreadLocal變量,如果必須使用,應(yīng)該在使用完以后手動清理。

內(nèi)存泄漏

內(nèi)存泄漏指得是程序中存在某些對象或資源沒有妥善的釋放。

導(dǎo)致這些對象或資源一直占用著內(nèi)存,從而無法被回收,隨著時間推移,這些未釋放的對象或資源會越來越多,最終耗盡系統(tǒng)內(nèi)存資源,導(dǎo)致系統(tǒng)崩潰。

常見的內(nèi)存泄漏

  • 對象被創(chuàng)建以后,沒有及時被銷毀,成為垃圾對象
  • 沒有正確關(guān)閉IO資源
  • 緩存沒有被清空
  • 形態(tài)集合類對象未刪除引用
  • 單列模式下對象未及時釋放

內(nèi)存溢出

內(nèi)存溢出是指程序在申請內(nèi)存時,無法獲得足夠的內(nèi)存空間,導(dǎo)致程序無法正常運(yùn)行。

通常情況下,當(dāng)程序需要使用的內(nèi)存超過系統(tǒng)能提供的內(nèi)存時,就會發(fā)生內(nèi)存溢出的情況。

常見的內(nèi)存溢出

  • 堆內(nèi)存溢出:由于創(chuàng)建了過多的對象或者某些對象太大,導(dǎo)致堆內(nèi)存不足。
  • 棧內(nèi)存溢出:由于方法調(diào)用過多或者某些方法的遞歸調(diào)用層數(shù)過多,導(dǎo)致棧內(nèi)存不足
  • 永久代內(nèi)存溢出:由于創(chuàng)建了過多的類或者字符串,導(dǎo)致永久代內(nèi)存不足。

Java中四種引用類型

引用關(guān)系的強(qiáng)弱關(guān)系:強(qiáng)引用>軟引用>弱引用>虛引用

強(qiáng)引用

如果一個對象具有強(qiáng)引用,它就不會被垃圾回收器回收。即使當(dāng)前內(nèi)存空間不足,JVM也不回回收它,而是拋出OutOfMemoryError錯誤,使程序終止異常,如果想中斷強(qiáng)引用和某個對象的關(guān)聯(lián),可以顯式的將引用類型賦值為null,這樣一來,JVM在合適的時間就會回收改對象。

軟引用

在使用軟引用時,如果內(nèi)存的空間足夠,軟引用就能繼續(xù)被使用,而不會被垃圾回收器回收,只有在內(nèi)存不足時,軟引用才會被垃圾回收器回收。(軟引用可以用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存,比如網(wǎng)頁緩存,圖片緩存等。使用軟引用能防止內(nèi)存泄漏,增強(qiáng)程序的健壯性)

弱引用

具有弱引用的對象擁有的生命周期更短。當(dāng)jvm進(jìn)行垃圾回收器回收,一旦發(fā)現(xiàn)弱引用對象,無論當(dāng)前內(nèi)存是否充足,都會將弱引用進(jìn)行回收

虛引用

虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。

注意點(diǎn)

虛引用必須和引用隊(duì)列聯(lián)合使用,當(dāng)垃圾回收器準(zhǔn)備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象內(nèi)存之前,把這個虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列之中。(其它引用是被JVM回收后才被加入引用隊(duì)列中的,由于這個機(jī)制,所以虛引用大多被用于引用銷毀前工作。可以使用在對象銷毀前的一些操作,比如資源釋放)

ThreadLocal示例

示例1

public class ThreadLocalDemo1 {
    private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
    private static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
    /**
     * 運(yùn)行 count個線程,每個線程持有自己獨(dú)有的 String類型編號
     */
    public void startThreadArray(int count) {
        Thread[] runs = new Thread[count];
        for (int i = 0; i < runs.length; i++) {
            // 賦值編號id
            new ThreadDemo1(i).start();
        }
    }
    /**
     * 線程類:
     */
    public static class ThreadDemo1 extends Thread {
        /**
         * 編號id
         */
        private int codeId;
        public ThreadDemo1(int codeId) {
            this.codeId = codeId;
        }
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            threadLocal1.set("threadLocal1賦值,線程_" + codeId);
            if (codeId == 2) {
                //如果是線程2,設(shè)置 threadLocal2變量,值乘以5
                threadLocal2.set(codeId * 5);
            }
            System.out.println(threadName + " -》 threadLocal1 " + threadLocal1.get());
            System.out.println(threadName + " -》 threadLocal2 " + threadLocal2.get());
            // 使用完移除,help GC
            threadLocal1.remove();
            threadLocal2.remove();
        }
    }
    public static void main(String[] args) {
        ThreadLocalDemo1 useDemo = new ThreadLocalDemo1();
        // 啟動3個線程
        useDemo.startThreadArray(3);
    }
}

 每個線程會獲取到屬于自己的線程值,不會有任何的錯亂

示例2

public class ThreadLocalDemo2 {
    /**
     * 初始化 num值。使用時,先通過get方法獲取。
     */
    public static ThreadLocal<ThreadLocalDemo2.Number> threadLocalValue = new ThreadLocal<ThreadLocalDemo2.Number>() {
        @Override
        protected Number initialValue() {
            return new Number(0);
        }
    };
    /**
     * 數(shù)據(jù)類
     */
    private static class Number {
        public Number(int num) {
            this.num = num;
        }
        private int num;
        public int getNum() {
            return num;
        }
        public void setNum(int num) {
            this.num = num;
        }
        @Override
        public String toString() {
            return "Number [num=" + num + "]";
        }
    }
    /**
     * 線程類:
     */
    public static class ThreadDemo2 extends Thread {
        @Override
        public void run() {
            // 如果沒有初始化,注意NPE。
            // static修飾的 number時,注釋掉這句
            Number number = threadLocalValue.get();
            //每個線程計(jì)數(shù)加隨機(jī)數(shù)
            Random r = new Random();
            number.setNum(number.getNum() + r.nextInt(100));
            //將其存儲到ThreadLocal中
            threadLocalValue.set(number);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印保存的隨機(jī)值
            System.out.println(Thread.currentThread().getName() + " -》 " + threadLocalValue.get().getNum());
            threadLocalValue.remove();
            System.out.println(Thread.currentThread().getName() + " remove方法之后 -》 " + threadLocalValue.get().getNum());
        }
    }
    public static void main(String[] args) {
        // 啟動5個線程
        for (int i = 0; i < 5; i++) {
            new ThreadDemo2().start();
        }
    }
}

每個線程可以通過initialValue方法初始化變量值。

到此這篇關(guān)于Java中的ThreadLocal線程變量詳解的文章就介紹到這了,更多相關(guān)Java中的ThreadLocal內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JAVA實(shí)現(xiàn)二維碼生成加背景圖代碼實(shí)例

    JAVA實(shí)現(xiàn)二維碼生成加背景圖代碼實(shí)例

    這篇文章主要介紹了JAVA實(shí)現(xiàn)二維碼生成加背景圖代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • Java內(nèi)存區(qū)域和內(nèi)存模型講解

    Java內(nèi)存區(qū)域和內(nèi)存模型講解

    今天小編就為大家分享一篇關(guān)于Java內(nèi)存區(qū)域和內(nèi)存模型講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • Java通過反射注解賦值的方法詳解

    Java通過反射注解賦值的方法詳解

    這篇文章主要為大家詳細(xì)介紹了Java語言如何通過反射實(shí)現(xiàn)注解賦值,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,需要的可以參考一下
    2022-07-07
  • SpringCloud中使用Sentinel實(shí)現(xiàn)限流的實(shí)戰(zhàn)

    SpringCloud中使用Sentinel實(shí)現(xiàn)限流的實(shí)戰(zhàn)

    限流在很多地方都可以使用的到,本篇博客將介紹如何使用SpringCloud中使用Sentinel實(shí)現(xiàn)限流,從而達(dá)到服務(wù)降級的目的,感興趣的可以了解一下
    2022-01-01
  • java實(shí)現(xiàn)圖片反色處理示例

    java實(shí)現(xiàn)圖片反色處理示例

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖片反色處理示例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • 修改若依框架Token的過期時間問題

    修改若依框架Token的過期時間問題

    本文介紹了如何修改若依框架中Token的過期時間,通過修改`application.yml`文件中的配置來實(shí)現(xiàn),默認(rèn)單位為分鐘,希望此經(jīng)驗(yàn)對大家有所幫助,也歡迎支持腳本之家
    2025-02-02
  • Spring IO Platform簡單介紹

    Spring IO Platform簡單介紹

    這篇文章主要介紹了Spring IO Platform簡單介紹,具有一定借鑒價值,需要的朋友可以參考下
    2017-12-12
  • Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析

    Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析

    這篇文章主要介紹了Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • 淺析Java中Map與HashMap,Hashtable,HashSet的區(qū)別

    淺析Java中Map與HashMap,Hashtable,HashSet的區(qū)別

    HashMap和Hashtable兩個類都實(shí)現(xiàn)了Map接口,二者保存K-V對(key-value對);HashSet則實(shí)現(xiàn)了Set接口,性質(zhì)類似于集合
    2013-09-09
  • MyBatis中@One的實(shí)現(xiàn)示例

    MyBatis中@One的實(shí)現(xiàn)示例

    @One注解在MyBatis中用于定義一對一關(guān)系映射,通過關(guān)聯(lián)查詢獲取其他表的數(shù)據(jù),并將其映射到另一個實(shí)體類上,下面就來具體介紹一下,感興趣的可以了解一下
    2025-11-11

最新評論

宕昌县| 尤溪县| 滁州市| 阳春市| 镇安县| 乾安县| 连城县| 韶关市| 开平市| 光山县| 阳春市| 资溪县| 绥芬河市| 佛冈县| 吉安市| 上饶市| 巨鹿县| 正镶白旗| 河间市| 汉沽区| 晴隆县| 株洲县| 卢湾区| 陆川县| 霞浦县| 温宿县| 高唐县| 怀仁县| 乐安县| 贵定县| 萍乡市| 久治县| 定陶县| 岑溪市| 武川县| 南郑县| 景德镇市| 双桥区| 大宁县| 淳安县| 嵩明县|