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

Java多線程 ThreadLocal原理解析

 更新時(shí)間:2021年10月28日 14:15:44   作者:冬日毛毛雨  
這篇文章主要介紹了Java多線程 ThreadLocal原理,ThreadLoal 變量,線程局部變量,同一個(gè) ThreadLocal 所包含的對(duì)象,在不同的 Thread 中有不同的副本,下面文章也是圍繞Java多線程 ThreadLocal展開(kāi)內(nèi)容,需要的朋友可以參考一下

1、什么是ThreadLocal變量

ThreadLoal 變量,線程局部變量,同一個(gè) ThreadLocal 所包含的對(duì)象,在不同的 Thread 中有不同的副本。

這里有幾點(diǎn)需要注意:

  • 因?yàn)槊總€(gè) Thread 內(nèi)有自己的實(shí)例副本,且該副本只能由當(dāng)前 Thread 使用。這是也是 ThreadLocal 命名的由來(lái)。
  • 既然每個(gè) Thread 有自己的實(shí)例副本,且其它 Thread 不可訪問(wèn),那就不存在多線程間共享的問(wèn)題。

ThreadLocal 提供了線程本地的實(shí)例。它與普通變量的區(qū)別在于,每個(gè)使用該變量的線程都會(huì)初始化一個(gè)完全獨(dú)立的實(shí)例副本。ThreadLocal 變量通常被private static修飾。當(dāng)一個(gè)線程結(jié)束時(shí),它所使用的所有 ThreadLocal 相對(duì)的實(shí)例副本都可被回收。

總的來(lái)說(shuō),ThreadLocal 適用于每個(gè)線程需要自己獨(dú)立的實(shí)例且該實(shí)例需要在多個(gè)方法中被使用,也即變量在線程間隔離而在方法或類(lèi)間共享的場(chǎng)景。

2、ThreadLocal實(shí)現(xiàn)原理

首先 ThreadLocal 是一個(gè)泛型類(lèi),保證可以接受任何類(lèi)型的對(duì)象。

因?yàn)橐粋€(gè)線程內(nèi)可以存在多個(gè) ThreadLocal 對(duì)象,所以其實(shí)是 ThreadLocal 內(nèi)部維護(hù)了一個(gè) Map ,這個(gè) Map 不是直接使用的 HashMap ,而是 ThreadLocal 實(shí)現(xiàn)的一個(gè)叫做 ThreadLocalMap 的靜態(tài)內(nèi)部類(lèi)。而我們使用的 get() 、set() 方法其實(shí)都是調(diào)用了這個(gè)ThreadLocalMap類(lèi)對(duì)應(yīng)的 get() set() 方法。

例如下面的 set 方法:

    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方法:

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

createMap方法:

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocalMap是個(gè)靜態(tài)的內(nèi)部類(lèi):

 

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
...
}

最終的變量是放在了當(dāng)前線程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。

3、內(nèi)存泄漏問(wèn)題

實(shí)際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點(diǎn)是,如果這個(gè)對(duì)象只存在弱引用,那么在下一次垃圾回收的時(shí)候必然會(huì)被清理掉。

所以如果 ThreadLocal 沒(méi)有被外部強(qiáng)引用的情況下,在垃圾回收的時(shí)候會(huì)被清理掉的,這樣一來(lái) ThreadLocalMap中使用這個(gè) ThreadLocal 的 key 也會(huì)被清理掉。但是,value 是強(qiáng)引用,不會(huì)被清理,這樣一來(lái)就會(huì)出現(xiàn) key 為 null 的 value。

ThreadLocalMap實(shí)現(xiàn)中已經(jīng)考慮了這種情況,在調(diào)用 set()get() 、remove() 方法的時(shí)候,會(huì)清理掉 key 為 null 的記錄。如果說(shuō)會(huì)出現(xiàn)內(nèi)存泄漏,那只有在出現(xiàn)了 key 為 null 的記錄后,沒(méi)有手動(dòng)調(diào)用 remove() 方法,并且之后也不再調(diào)用 get() 、set() 、remove() 方法的情況下。

4、使用場(chǎng)景

如上文所述,ThreadLocal 適用于如下兩種場(chǎng)景

每個(gè)線程需要有自己?jiǎn)为?dú)的實(shí)例
實(shí)例需要在多個(gè)方法中共享,但不希望被多線程共享
對(duì)于第一點(diǎn),每個(gè)線程擁有自己實(shí)例,實(shí)現(xiàn)它的方式很多。例如可以在線程內(nèi)部構(gòu)建一個(gè)單獨(dú)的實(shí)例。ThreadLoca 可以以非常方便的形式滿足該需求。

對(duì)于第二點(diǎn),可以在滿足第一點(diǎn)(每個(gè)線程有自己的實(shí)例)的條件下,通過(guò)方法間引用傳遞的形式實(shí)現(xiàn)。ThreadLocal 使得代碼耦合度更低,且實(shí)現(xiàn)更優(yōu)雅。

1)存儲(chǔ)用戶Session

一個(gè)簡(jiǎn)單的用ThreadLocal來(lái)存儲(chǔ)Session的例子:

private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }

2)解決線程安全的問(wèn)題

比如Java7中的SimpleDateFormat不是線程安全的,可以用ThreadLocal來(lái)解決這個(gè)問(wèn)題:

public class DateUtil {
    private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static String formatDate(Date date) {
        return format1.get().format(date);
    }
}

這里的DateUtil.formatDate()就是線程安全的了。(Java8里的 [java.time.format.DateTimeFormatter]

(http://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) 是線程安全的,Joda time里的DateTimeFormat也是線程安全的)。

public class Context {

    private String name;
    private String cardId;

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class ExecutionTask implements Runnable {

    private QueryFromDBAction queryAction = new QueryFromDBAction();

    private QueryFromHttpAction httpAction = new QueryFromHttpAction();

    @Override
    public void run() {

        final Context context = new Context();
        queryAction.execute(context);
        System.out.println("The name query successful");
        httpAction.execute(context);
        System.out.println("The cardId query successful");

        System.out.println("The Name is " + context.getName() + " and CardId " + context.getCardId());
    }
}

public class QueryFromDBAction {

    public void execute(Context context) {

        try {
            Thread.sleep(1000L);
            String name = "Jack " + Thread.currentThread().getName();
            context.setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


    public void execute(Context context) {
        String name = context.getName();
        String cardId = getCardId(name);
        context.setCardId(cardId);
    }

    private String getCardId(String name) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "444555" + Thread.currentThread().getId();
    }
}

public class ContextTest {

    public static void main(String[] args) {

        IntStream.range(1, 5)
                .forEach(i ->
                        new Thread(new ExecutionTask()).start()
                );
    }
}

The name query successful
The name query successful
The name query successful
The name query successful
The cardId query successful
The Name is Jack Thread-0 and CardId 44455511
The cardId query successful
The Name is Jack Thread-1 and CardId 44455512
The cardId query successful
The Name is Jack Thread-2 and CardId 44455513
The cardId query successful
The Name is Jack Thread-3 and CardId 44455514

問(wèn)題:需要在每個(gè)調(diào)用Context的方法中傳入進(jìn)去

public void execute(Context context) {
}

3)使用ThreadLocal重新設(shè)計(jì)一個(gè)上下文設(shè)計(jì)模式

public final class ActionContext {

    private static final ThreadLocal<Context> threadLocal = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new Context();
        }
    };

    public static ActionContext getActionContext() {
        return ContextHolder.actionContext;
    }

    public Context getContext() {

        return threadLocal.get();
    }

    private static class ContextHolder {
        private final static ActionContext actionContext = new ActionContext();

    }
}

public class Context {

    private String name;
    private String cardId;

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class ExecutionTask implements Runnable {

    private QueryFromDBAction queryAction = new QueryFromDBAction();

    private QueryFromHttpAction httpAction = new QueryFromHttpAction();

    @Override
    public void run() {

        queryAction.execute();
        System.out.println("The name query successful");
        httpAction.execute();
        System.out.println("The cardId query successful");

        final Context context = ActionContext.getActionContext().getContext();
        System.out.println("The Name is " + context.getName() + " and CardId " + context.getCardId());
    }
}
public class QueryFromDBAction {

    public void execute() {

        try {
            Thread.sleep(1000L);
            String name = "Jack " + Thread.currentThread().getName();
            ActionContext.getActionContext().getContext().setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class QueryFromHttpAction {

    public void execute() {
        Context context = ActionContext.getActionContext().getContext();
        String name = context.getName();
        String cardId = getCardId(name);
        context.setCardId(cardId);


    }

    private String getCardId(String name) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "444555" + Thread.currentThread().getId();
    }
}

public class ContextTest {

    public static void main(String[] args) {

        IntStream.range(1, 5)
                .forEach(i ->
                        new Thread(new ExecutionTask()).start()
                );
    }
}

The name query successful
The name query successful
The name query successful
The name query successful
The cardId query successful
The Name is Jack Thread-3 and CardId 44455514
The cardId query successful
The cardId query successful
The Name is Jack Thread-0 and CardId 44455511
The cardId query successful
The Name is Jack Thread-2 and CardId 44455513
The Name is Jack Thread-1 and CardId 44455512

這樣寫(xiě) 執(zhí)行過(guò)程中不會(huì)看到context的定義和聲明

注意:在使用之前記得將上個(gè)線程中context舊值清除調(diào),否則會(huì)重復(fù)調(diào)用(比如線程池操作)

4)ThreadLocal注意事項(xiàng)

臟數(shù)據(jù)

線程復(fù)用會(huì)產(chǎn)生臟數(shù)據(jù)。由于結(jié)程池會(huì)重用Thread對(duì)象,那么與Thread綁定的類(lèi)的靜態(tài)屬性ThreadLocal變量也會(huì)被重用。如果在實(shí)現(xiàn)的線程run()方法體中不顯式地調(diào)用remove() 清理與線程相關(guān)的ThreadLocal信息,那么倘若下一個(gè)結(jié)程不調(diào)用set() 設(shè)置初始值,就可能get() 到重用的線程信息,包括 ThreadLocal所關(guān)聯(lián)的線程對(duì)象的value值。

內(nèi)存泄漏

通常我們會(huì)使用使用static關(guān)鍵字來(lái)修飾ThreadLocal(這也是在源碼注釋中所推薦的)。在此場(chǎng)景下,其生命周期就不會(huì)隨著線程結(jié)束而結(jié)束,寄希望于ThreadLocal對(duì)象失去引用后,觸發(fā)弱引用機(jī)制來(lái)回收EntryValue就不現(xiàn)實(shí)了。如果不進(jìn)行remove() 操作,那么這個(gè)線程執(zhí)行完成后,通過(guò)ThreadLocal對(duì)象持有的對(duì)象是不會(huì)被釋放的。

以上兩個(gè)問(wèn)題的解決辦法很簡(jiǎn)單,就是在每次用完ThreadLocal時(shí), 必須要及時(shí)調(diào)用 remove()方法清理。

父子線程共享線程變量

很多場(chǎng)景下通過(guò)ThreadLocal來(lái)透?jìng)魅稚舷挛模瑫?huì)發(fā)現(xiàn)子線程的value和主線程不一致。比如用ThreadLocal來(lái)存儲(chǔ)監(jiān)控系統(tǒng)的某個(gè)標(biāo)記位,暫且命名為traceId。某次請(qǐng)求下所有的traceld都是一致的,以獲得可以統(tǒng)一解析的日志文件。但在實(shí)際開(kāi)發(fā)過(guò)程中,發(fā)現(xiàn)子線程里的traceld為null,跟主線程的并不一致。這就需要使用InheritableThreadLocal來(lái)解決父子線程之間共享線程變量的問(wèn)題,使整個(gè)連接過(guò)程中的traceId一致。

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

相關(guān)文章

  • java中 利用正則表達(dá)式提取( )內(nèi)內(nèi)容

    java中 利用正則表達(dá)式提取( )內(nèi)內(nèi)容

    本篇文章,小編為大家介紹關(guān)于java中 利用正則表達(dá)式提取( )內(nèi)內(nèi)容,有需要的朋友可以參考一下
    2013-04-04
  • shiro與spring集成基礎(chǔ)Hello案例詳解

    shiro與spring集成基礎(chǔ)Hello案例詳解

    這篇文章主要介紹了shiro與spring集成基礎(chǔ)Hello案例詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • Java使用同步方法解決銀行取錢(qián)的安全問(wèn)題案例分析

    Java使用同步方法解決銀行取錢(qián)的安全問(wèn)題案例分析

    這篇文章主要介紹了Java使用同步方法解決銀行取錢(qián)的安全問(wèn)題,結(jié)合具體案例形式分析了java同步方法實(shí)現(xiàn)多線程安全操作銀行取錢(qián)問(wèn)題,需要的朋友可以參考下
    2019-09-09
  • Jmeter測(cè)試必知的名詞及環(huán)境搭建

    Jmeter測(cè)試必知的名詞及環(huán)境搭建

    我們本章開(kāi)始學(xué)習(xí)Jmeter,后續(xù)還會(huì)有RF以及LoadRunner 的介紹,為什么要學(xué)習(xí)Jmeter,它主要是用來(lái)做性能測(cè)試的,其中它也需要間接或直接的需要用到抓包工具
    2021-09-09
  • Spring Boot整合郵件發(fā)送與注意事項(xiàng)

    Spring Boot整合郵件發(fā)送與注意事項(xiàng)

    這篇文章主要給大家介紹了關(guān)于Spring Boot整合郵件發(fā)送與注意事項(xiàng)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • Java利用Sping框架編寫(xiě)RPC遠(yuǎn)程過(guò)程調(diào)用服務(wù)的教程

    Java利用Sping框架編寫(xiě)RPC遠(yuǎn)程過(guò)程調(diào)用服務(wù)的教程

    這篇文章主要介紹了Java利用Sping框架編寫(xiě)RPC遠(yuǎn)程過(guò)程調(diào)用服務(wù)的教程,包括項(xiàng)目管理工具M(jìn)aven的搭配使用方法,需要的朋友可以參考下
    2016-06-06
  • JPA框架實(shí)現(xiàn)分頁(yè)查詢和條件查詢功能詳解

    JPA框架實(shí)現(xiàn)分頁(yè)查詢和條件查詢功能詳解

    這篇文章主要介紹了JPA框架實(shí)現(xiàn)分頁(yè)查詢和條件查詢功能,JPA是Java Persistence API的簡(jiǎn)稱,在過(guò)去很多數(shù)據(jù)庫(kù)的增刪查改操作都是用這個(gè)框架操作的,感興趣想要詳細(xì)了解可以參考下文
    2023-05-05
  • SpringBoot配置和切換Tomcat流程詳解

    SpringBoot配置和切換Tomcat流程詳解

    這篇文章主要介紹了如何給springboot配置和切換默認(rèn)的Tomcat容器以及相關(guān)的經(jīng)驗(yàn)技巧,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • spring boot 配置freemarker及如何使用freemarker渲染頁(yè)面

    spring boot 配置freemarker及如何使用freemarker渲染頁(yè)面

    springboot中自帶的頁(yè)面渲染工具為thymeleaf 還有freemarker這兩種模板引擎,本文重點(diǎn)給大家介紹spring boot 配置freemarker及如何使用freemarker渲染頁(yè)面,感興趣的朋友一起看看吧
    2023-10-10
  • Scala 操作Redis使用連接池工具類(lèi)RedisUtil

    Scala 操作Redis使用連接池工具類(lèi)RedisUtil

    這篇文章主要介紹了Scala 操作Redis使用連接池工具類(lèi)RedisUtil,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06

最新評(píng)論