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

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)境下訪問時能保證各個線程的變量相對獨立于其他線程內(nèi)的變量。

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

特點

  • 每個線程都有自己的線程局部變量,且只能訪問自己的,不能訪問其他線程的
  • 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實現(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. 每個線程需要有自己的獨立實例
  2. 實例需要在多個方法中共享,但不希望被多線程共享

場景舉例:用來存儲用戶信息,數(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)存不足時,軟引用才會被垃圾回收器回收。(軟引用可以用來實現(xiàn)內(nèi)存敏感的高速緩存,比如網(wǎng)頁緩存,圖片緩存等。使用軟引用能防止內(nèi)存泄漏,增強(qiáng)程序的健壯性)

弱引用

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

虛引用

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

注意點

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

ThreadLocal示例

示例1

public class ThreadLocalDemo1 {
    private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
    private static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
    /**
     * 運(yùn)行 count個線程,每個線程持有自己獨有的 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();
            //每個線程計數(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)文章

  • 使用maven-assembly-plugin如何將system 依賴范圍的jar以class 方式打包進(jìn) jar包中

    使用maven-assembly-plugin如何將system 依賴范圍的jar以class 方式

    這篇文章主要介紹了使用maven-assembly-plugin如何將system 依賴范圍的jar以class 方式打包進(jìn) jar包中,本文給大家分享完美解決思路,結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • SpringBoot使用PageHelper插件實現(xiàn)Mybatis分頁效果

    SpringBoot使用PageHelper插件實現(xiàn)Mybatis分頁效果

    這篇文章主要介紹了SpringBoot使用PageHelper插件實現(xiàn)Mybatis分頁效果,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的參考借鑒價值,需要的朋友可以參考下
    2024-02-02
  • springboot創(chuàng)建多module項目的實例

    springboot創(chuàng)建多module項目的實例

    這篇文章主要介紹了springboot創(chuàng)建多module項目的實例代碼,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • mybatis向數(shù)據(jù)庫里插入記錄后自動返回記錄ID問題

    mybatis向數(shù)據(jù)庫里插入記錄后自動返回記錄ID問題

    本文介紹了在接手項目時,對一個業(yè)務(wù)處理邏輯進(jìn)行重構(gòu)和性能優(yōu)化的經(jīng)歷,作者提到,性能問題可能是導(dǎo)致bug的一個重要原因,作者提到,在以前的.NET項目中,插入記錄后系統(tǒng)會自動刷新實體類,為其中的主鍵ID賦值,而SpringBoot項目mybatis也可以通過指定主鍵來優(yōu)化代碼
    2025-01-01
  • 一文帶你了解Java中的Object類及類中方法

    一文帶你了解Java中的Object類及類中方法

    Object是Java默認(rèn)提供的一個類。Java里面除了Object類,所有的類都是存在繼承關(guān)系的。默認(rèn)會繼承Object父?類。即所有類的對象都可以使用Object的引用進(jìn)行接收。本文就來為大家詳細(xì)講講Object類及類中方法,感興趣的可以了解一下
    2022-08-08
  • 樹,二叉樹(完全二叉樹,滿二叉樹)概念圖解

    樹,二叉樹(完全二叉樹,滿二叉樹)概念圖解

    今天小編就為大家分享一篇關(guān)于二叉樹的圖文詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧,希望能對你有所幫助
    2021-07-07
  • Prometheus 入門教程之SpringBoot 實現(xiàn)自定義指標(biāo)監(jiān)控

    Prometheus 入門教程之SpringBoot 實現(xiàn)自定義指標(biāo)監(jiān)控

    這篇文章主要介紹了Prometheus 入門教程之SpringBoot 實現(xiàn)自定義指標(biāo)監(jiān)控,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • SpringMVC中redirect重定向(帶參數(shù))的3種方式

    SpringMVC中redirect重定向(帶參數(shù))的3種方式

    Spring MVC中做form表單功能提交時,防止用戶客戶端后退或者刷新時重復(fù)提交問題,需要在服務(wù)端進(jìn)行重定向跳轉(zhuǎn),本文主要介紹了SpringMVC中redirect重定向(帶參數(shù))的3種方式,感興趣的可以了解一下
    2024-07-07
  • 深入理解java中的null“類型”

    深入理解java中的null“類型”

    這篇文章主要介紹了深入理解java中的null“類型”,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Java中正則表達(dá)式的語法以及matches方法的使用方法

    Java中正則表達(dá)式的語法以及matches方法的使用方法

    正則表達(dá)式(Regular Expression)是一門簡單語言的語法規(guī)范,是強(qiáng)大、便捷、高效的文本處理工具,這篇文章主要給大家介紹了關(guān)于Java中正則表達(dá)式的語法以及matches方法的使用方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-05-05

最新評論