成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java基礎(chǔ)進階之ThreadLocal詳解

worldligang / 3063人閱讀

摘要:基本在項目開發(fā)中基本不會用到但是面試官是比較喜歡問這類問題的所以還是有必要了解一下該類的功能與原理的是什么是一個將在多線程中為每一個線程創(chuàng)建多帶帶的變量副本的類當(dāng)使用來維護變量時會為每個線程創(chuàng)建多帶帶的變量副本避免因多線程操作共享變量而導(dǎo)致的數(shù)

ThreadLocal基本在項目開發(fā)中基本不會用到, 但是面試官是比較喜歡問這類問題的;所以還是有必要了解一下該類的功能與原理的.
ThreadLocal是什么

ThreadLocal是一個將在多線程中為每一個線程創(chuàng)建多帶帶的變量副本的類; 當(dāng)使用ThreadLocal來維護變量時, ThreadLocal會為每個線程創(chuàng)建多帶帶的變量副本, 避免因多線程操作共享變量而導(dǎo)致的數(shù)據(jù)不一致的情況;

ThreadLocal類用在哪些場景

一般來說, ThreadLocal在實際工業(yè)生產(chǎn)中并不常見, 但是在很多框架中使用卻能夠解決一些框架問題; 比如Spring中的事務(wù)、Spring 中 作用域 ScopeRequest的Bean 使用ThreadLocal來解決.

ThreadLocal使用方法

1、將需要被多線程訪問的屬性使用ThreadLocal變量來定義; 下面以網(wǎng)上多數(shù)舉例的DBConnectionFactory類為例來舉例

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBConnectionFactory {

    private static final ThreadLocal dbConnectionLocal = new ThreadLocal() {
        @Override
        protected Connection initialValue() {
            try {
                return DriverManager.getConnection("", "", "");
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    };

    public Connection getConnection() {
        return dbConnectionLocal.get();
    }
}

這樣在Client獲取Connection的時候, 每個線程獲取到的Connection都是該線程獨有的, 做到Connection的線程隔離; 所以并不存在線程安全問題

ThreadLocal如何實現(xiàn)線程隔離

1、主要是用到了Thread對象中的一個ThreadLocalMap類型的變量threadLocals, 負責(zé)存儲當(dāng)前線程的關(guān)于Connection的對象, 以dbConnectionLocal 這個變量為Key, 以新建的Connection對象為Value; 這樣的話, 線程第一次讀取的時候如果不存在就會調(diào)用ThreadLocalinitialValue方法創(chuàng)建一個Connection對象并且返回;

具體關(guān)于為線程分配變量副本的代碼如下:

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();
}

1、首先獲取當(dāng)前線程對象t, 然后從線程t中獲取到ThreadLocalMap的成員屬性threadLocals

2、如果當(dāng)前線程的threadLocals已經(jīng)初始化(即不為null) 并且存在以當(dāng)前ThreadLocal對象為Key的值, 則直接返回當(dāng)前線程要獲取的對象(本例中為Connection);

3、如果當(dāng)前線程的threadLocals已經(jīng)初始化(即不為null)但是不存在以當(dāng)前ThreadLocal對象為Key的的對象, 那么重新創(chuàng)建一個Connection對象, 并且添加到當(dāng)前線程的threadLocals Map中,并返回

4、如果當(dāng)前線程的threadLocals屬性還沒有被初始化, 則重新創(chuàng)建一個ThreadLocalMap對象, 并且創(chuàng)建一個Connection對象并添加到ThreadLocalMap對象中并返回。

如果存在則直接返回很好理解, 那么對于如何初始化的代碼又是怎樣的呢?

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;
}

1、首先調(diào)用我們上面寫的重載過后的initialValue方法, 產(chǎn)生一個Connection對象

2、繼續(xù)查看當(dāng)前線程的threadLocals是不是空的, 如果ThreadLocalMap已被初始化, 那么直接將產(chǎn)生的對象添加到ThreadLocalMap中, 如果沒有初始化, 則創(chuàng)建并添加對象到其中;

同時, ThreadLocal還提供了直接操作Thread對象中的threadLocals的方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

這樣我們也可以不實現(xiàn)initialValue, 將初始化工作放到DBConnectionFactorygetConnection方法中:

public Connection getConnection() {
    Connection connection = dbConnectionLocal.get();
    if (connection == null) {
        try {
            connection = DriverManager.getConnection("", "", "");
            dbConnectionLocal.set(connection);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    return connection;
}

那么我們看過代碼之后就很清晰的知道了為什么ThreadLocal能夠?qū)崿F(xiàn)變量的多線程隔離了; 其實就是用了Map的數(shù)據(jù)結(jié)構(gòu)給當(dāng)前線程緩存了, 要使用的時候就從本線程的threadLocals對象中獲取就可以了, key就是當(dāng)前線程;

當(dāng)然了在當(dāng)前線程下獲取當(dāng)前線程里面的Map里面的對象并操作肯定沒有線程并發(fā)問題了, 當(dāng)然能做到變量的線程間隔離了;

現(xiàn)在我們知道了ThreadLocal到底是什么了, 又知道了如何使用ThreadLocal以及其基本實現(xiàn)原理了是不是就可以結(jié)束了呢? 其實還有一個問題就是ThreadLocalMap是個什么對象, 為什么要用這個對象呢?

ThreadLocalMap對象是什么

本質(zhì)上來講, 它就是一個Map, 但是這個ThreadLocalMap與我們平時見到的Map有點不一樣

1、它沒有實現(xiàn)Map接口;

2、它沒有public的方法, 最多有一個default的構(gòu)造方法, 因為這個ThreadLocalMap的方法僅僅在ThreadLocal類中調(diào)用, 屬于靜態(tài)內(nèi)部類

3、ThreadLocalMap的Entry實現(xiàn)繼承了WeakReference>

4、該方法僅僅用了一個Entry數(shù)組來存儲Key, Value; Entry并不是鏈表形式, 而是每個bucket里面僅僅放一個Entry;

要了解ThreadLocalMap的實現(xiàn), 我們先從入口開始, 就是往該Map中添加一個值:

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();
}

先進行簡單的分析, 對該代碼表層意思進行解讀:

1、看下當(dāng)前threadLocal的在數(shù)組中的索引位置 比如: `i = 2`, 看 `i = 2` 位置上面的元素(Entry)的`Key`是否等于threadLocal 這個 Key, 如果等于就很好說了, 直接將該位置上面的Entry的Value替換成最新的就可以了;

2、如果當(dāng)前位置上面的 Entry 的 Key為空, 說明ThreadLocal對象已經(jīng)被回收了, 那么就調(diào)用replaceStaleEntry

3、如果清理完無用條目(ThreadLocal被回收的條目)、并且數(shù)組中的數(shù)據(jù)大小 > 閾值的時候?qū)Ξ?dāng)前的Table進行重新哈希

所以, 該HashMap是處理沖突檢測的機制是向后移位, 清除過期條目 最終找到合適的位置;

了解完Set方法, 后面就是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);
}

先找到ThreadLocal的索引位置, 如果索引位置處的entry不為空并且鍵與threadLocal是同一個對象, 則直接返回; 否則去后面的索引位置繼續(xù)查找;

使用ThreadLocal造成內(nèi)存泄露
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo {
    static class LocalVariable {
        private Long[] a = new Long[1024 * 1024];
    }

    // (1)
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());
    // (2)
    final static ThreadLocal localVariable = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        // (3)
        Thread.sleep(5000 * 4);
        for (int i = 0; i < 50; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    // (4)
                    localVariable.set(new LocalVariable());
                    // (5)
                    System.out.println("use local varaible" + localVariable.get());
                    localVariable.remove();
                }
            });
        }
        // (6)
        System.out.println("pool execute over");
    }
}

我在網(wǎng)上找到一個樣例, 如果用線程池來操作ThreadLocal 對象確實會造成內(nèi)存泄露, 因為對于線程池里面不會銷毀的線程, 里面總會存在著的強引用, 因為final static 修飾的 ThreadLocal 并不會釋放, 而ThreadLocalMap 對于 Key 雖然是弱引用, 但是強引用不會釋放, 弱引用當(dāng)然也會一直有值, 同時創(chuàng)建的LocalVariable對象也不會釋放, 就造成了內(nèi)存泄露; 如果LocalVariable對象不是一個大對象的話, 其實泄露的并不嚴重, 泄露的內(nèi)存 = 核心線程數(shù) * LocalVariable對象的大小;

所以, 為了避免出現(xiàn)內(nèi)存泄露的情況, ThreadLocal提供了一個清除線程中對象的方法, 即 remove, 其實內(nèi)部實現(xiàn)就是調(diào)用 ThreadLocalMapremove方法:

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;
        }
    }
}

找到Key對應(yīng)的Entry, 并且清除Entry的Key(ThreadLocal)置空, 隨后清除過期的Entry即可避免內(nèi)存泄露;

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/73547.html

相關(guān)文章

  • Java 總結(jié)

    摘要:中的詳解必修個多線程問題總結(jié)個多線程問題總結(jié)有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升開源的運行原理從虛擬機工作流程看運行原理。 自己實現(xiàn)集合框架 (三): 單鏈表的實現(xiàn) 自己實現(xiàn)集合框架 (三): 單鏈表的實現(xiàn) 基于 POI 封裝 ExcelUtil 精簡的 Excel 導(dǎo)入導(dǎo)出 由于 poi 本身只是針對于 ...

    caspar 評論0 收藏0
  • Android 進階

    摘要:理解內(nèi)存模型對多線程編程無疑是有好處的。干貨高級動畫高級動畫進階,矢量動畫。 這是最好的Android相關(guān)原創(chuàng)知識體系(100+篇) 知識體系從2016年開始構(gòu)建,所有的文章都是圍繞著這個知識體系來寫,目前共收入了100多篇原創(chuàng)文章,其中有一部分未收入的文章在我的新書《Android進階之光》中。最重要的是,這個知識體系仍舊在成長中。 Android 下拉刷新庫,這一個就夠了! 新鮮出...

    DoINsiSt 評論0 收藏0
  • Java核心技術(shù)教程整理,長期更新

    以下是Java技術(shù)棧微信公眾號發(fā)布的關(guān)于 Java 的技術(shù)干貨,從以下幾個方面匯總。 Java 基礎(chǔ)篇 Java 集合篇 Java 多線程篇 Java JVM篇 Java 進階篇 Java 新特性篇 Java 工具篇 Java 書籍篇 Java基礎(chǔ)篇 8張圖帶你輕松溫習(xí) Java 知識 Java父類強制轉(zhuǎn)換子類原則 一張圖搞清楚 Java 異常機制 通用唯一標(biāo)識碼UUID的介紹及使用 字符串...

    Anchorer 評論0 收藏0
  • Java面試題必備知識ThreadLocal

    摘要:方法,刪除當(dāng)前線程綁定的這個副本數(shù)字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據(jù)參考資料所說,選擇這個數(shù)字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關(guān)于ThreadLocal常見的疑問,希望可以通過這篇學(xué)...

    Maxiye 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<