成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

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

worldligang / 3101人閱讀

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

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

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

ThreadLocal類用在哪些場(chǎng)景

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

ThreadLocal使用方法

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

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的時(shí)候, 每個(gè)線程獲取到的Connection都是該線程獨(dú)有的, 做到Connection的線程隔離; 所以并不存在線程安全問(wèn)題

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

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

具體關(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)前線程對(duì)象t, 然后從線程t中獲取到ThreadLocalMap的成員屬性threadLocals

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

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

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

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

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)用我們上面寫的重載過(guò)后的initialValue方法, 產(chǎn)生一個(gè)Connection對(duì)象

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

同時(shí), ThreadLocal還提供了直接操作Thread對(duì)象中的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);
}

這樣我們也可以不實(shí)現(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;
}

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

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

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

ThreadLocalMap對(duì)象是什么

本質(zhì)上來(lái)講, 它就是一個(gè)Map, 但是這個(gè)ThreadLocalMap與我們平時(shí)見(jiàn)到的Map有點(diǎn)不一樣

1、它沒(méi)有實(shí)現(xiàn)Map接口;

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

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

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

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

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

先進(jìn)行簡(jiǎn)單的分析, 對(duì)該代碼表層意思進(jìn)行解讀:

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

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

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

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

了解完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是同一個(gè)對(duì)象, 則直接返回; 否則去后面的索引位置繼續(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)上找到一個(gè)樣例, 如果用線程池來(lái)操作ThreadLocal 對(duì)象確實(shí)會(huì)造成內(nèi)存泄露, 因?yàn)閷?duì)于線程池里面不會(huì)銷毀的線程, 里面總會(huì)存在著的強(qiáng)引用, 因?yàn)?b>final static 修飾的 ThreadLocal 并不會(huì)釋放, 而ThreadLocalMap 對(duì)于 Key 雖然是弱引用, 但是強(qiáng)引用不會(huì)釋放, 弱引用當(dāng)然也會(huì)一直有值, 同時(shí)創(chuàng)建的LocalVariable對(duì)象也不會(huì)釋放, 就造成了內(nèi)存泄露; 如果LocalVariable對(duì)象不是一個(gè)大對(duì)象的話, 其實(shí)泄露的并不嚴(yán)重, 泄露的內(nèi)存 = 核心線程數(shù) * LocalVariable對(duì)象的大小;

所以, 為了避免出現(xiàn)內(nèi)存泄露的情況, ThreadLocal提供了一個(gè)清除線程中對(duì)象的方法, 即 remove, 其實(shí)內(nèi)部實(shí)現(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對(duì)應(yīng)的Entry, 并且清除Entry的Key(ThreadLocal)置空, 隨后清除過(guò)期的Entry即可避免內(nèi)存泄露;

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/73547.html

相關(guān)文章

  • Java 總結(jié)

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

    caspar 評(píng)論0 收藏0
  • Android 進(jìn)階

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

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

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

    Anchorer 評(píng)論0 收藏0
  • Java面試題必備知識(shí)ThreadLocal

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

    Maxiye 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<