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

資訊專欄INFORMATION COLUMN

(基礎(chǔ)系列)ThreadLocal的用法、原理和用途

bitkylin / 1320人閱讀

摘要:那線程局部變量就是每個(gè)線程都會有一個(gè)局部變量,獨(dú)立于變量的初始化副本,而各個(gè)副本是通過線程唯一標(biāo)識相關(guān)聯(lián)的。移除此線程局部變量當(dāng)前線程的值。如果此線程局部變量隨后被當(dāng)前線程讀取,且這期間當(dāng)前線程沒有設(shè)置其值,則將調(diào)用其方法重新初始化其值。

前言

ThreadLocal網(wǎng)上資料很多,那我為什么還要寫下這篇文章呢?主要是想?yún)R聚多篇文章的優(yōu)秀之處以及我對于ThreadLocal的理解來加深印象,也使讀者能更全面的理解ThreadLocal的用法、原理和用途。

一、何謂“ThreadLocal”

ThreadLocal是一個(gè)線程局部變量,我們都知道全局變量和局部變量的區(qū)別,拿Java舉例就是定義在類中的是全局的變量,各個(gè)方法中都能訪問得到,而局部變量定義在方法中,只能在方法內(nèi)訪問。那線程局部變量(ThreadLocal)就是每個(gè)線程都會有一個(gè)局部變量,獨(dú)立于變量的初始化副本,而各個(gè)副本是通過線程唯一標(biāo)識相關(guān)聯(lián)的。

二、ThreadLocal的用法

(1)方法摘要

作用域 類型 方法 描述
public T get() 返回此線程局部變量的當(dāng)前線程副本中的值
protected T initialValue() 返回此線程局部變量的當(dāng)前線程的“初始值”
public void remove() 移除此線程局部變量當(dāng)前線程的值
public void set(T value) 將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值

注意事項(xiàng)
==initialValue()== 這個(gè)方法是為了讓子類覆蓋設(shè)計(jì)的,默認(rèn)缺省null。如果get()后又remove()則可能會在調(diào)用一下此方法。
==remove()== 移除此線程局部變量當(dāng)前線程的值。如果此線程局部變量隨后被當(dāng)前線程 讀取,且這期間當(dāng)前線程沒有 設(shè)置其值,則將調(diào)用其 initialValue() 方法重新初始化其值。這將導(dǎo)致在當(dāng)前線程多次調(diào)用 initialValue 方法。

(2)常規(guī)用法

在開始之前貼出一個(gè)公共的線程測試類

public class TaskThread extends Thread{

    private T t;

    public TaskThread(String threadName,T t) {
        this.setName(threadName);
        this.t = t;
    }

    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {

            try {
                Class[] argsClass = new Class[0];
                Method method = t.getClass().getMethod("getUniqueId",argsClass);
                int value = (int) method.invoke(t);
                System.out.println("thread[" + Thread.currentThread().getName() + "] --> uniqueId["+value+ "]");

            } catch (NoSuchMethodException e) {
                // TODO 暫不處理
                continue;

            } catch (IllegalAccessException e) {
                // TODO 暫不處理
                continue;

            } catch (InvocationTargetException e) {
                // TODO 暫不處理
                continue;

            }


        }
    }

}

例1:為每個(gè)線程生成一個(gè)唯一的局部標(biāo)識

public class UniqueThreadIdGenerator {

    // 原子整型
    private static final AtomicInteger uniqueId = new AtomicInteger(0);

    // 線程局部整型變量
    private static final ThreadLocal  uniqueNum =
            new ThreadLocal < Integer > () {
                @Override protected Integer initialValue() {
                    return uniqueId.getAndIncrement();
                }
            };

    //變量值
    public static int getUniqueId() {
        return uniqueId.get();
    }

    public static void main(String[] args) {
        UniqueThreadIdGenerator uniqueThreadId = new UniqueThreadIdGenerator();
        // 為每個(gè)線程生成一個(gè)唯一的局部標(biāo)識
        TaskThread t1 = new TaskThread("custom-thread-1", uniqueThreadId);
        TaskThread t2 = new TaskThread("custom-thread-2", uniqueThreadId);
        TaskThread t3 = new TaskThread("custom-thread-3", uniqueThreadId);
        t1.start();
        t2.start();
        t3.start();
    }

}

運(yùn)行結(jié)果:

//每個(gè)線程的局部變量都是唯一的
thread[custom-thread-2] --> uniqueId[0]
thread[custom-thread-2] --> uniqueId[0]
thread[custom-thread-1] --> uniqueId[0]
thread[custom-thread-1] --> uniqueId[0]
thread[custom-thread-3] --> uniqueId[0]
thread[custom-thread-3] --> uniqueId[0]

例2:為每個(gè)線程創(chuàng)建一個(gè)局部唯一的序列

public class UniqueSequenceGenerator {

    // 線程局部整型變量
    private static final ThreadLocal  uniqueNum =
            new ThreadLocal < Integer > () {
                @Override protected Integer initialValue() {
                    return 0;
                }
            };

    //變量值
    public static int getUniqueId() {
        uniqueNum.set(uniqueNum.get() + 1);
        return uniqueNum.get();
    }

    public static void main(String[] args) {
        UniqueSequenceGenerator uniqueThreadId = new UniqueSequenceGenerator();
        // 為每個(gè)線程生成內(nèi)部唯一的序列號
        TaskThread t1 = new TaskThread("custom-thread-1", uniqueThreadId);
        TaskThread t2 = new TaskThread("custom-thread-2", uniqueThreadId);
        TaskThread t3 = new TaskThread("custom-thread-3", uniqueThreadId);
        t1.start();
        t2.start();
        t3.start();
    }

}

運(yùn)行結(jié)果:

thread[custom-thread-2] --> uniqueId[1]
thread[custom-thread-2] --> uniqueId[2]
thread[custom-thread-1] --> uniqueId[1]
thread[custom-thread-1] --> uniqueId[2]
thread[custom-thread-3] --> uniqueId[1]
thread[custom-thread-3] --> uniqueId[2]
三、ThreadLocal的原理(摘自網(wǎng)上)

(1)源碼解析

源碼實(shí)現(xiàn)片段:set

/** 
    * Sets the current thread"s copy of this thread-local variable 
    * to the specified value.  Most subclasses will have no need to 
    * override this method, relying solely on the {@link #initialValue} 
    * method to set the values of thread-locals. 
    * 
    * @param value the value to be stored in the current thread"s copy of 
    *        this thread-local. 
    */  
   public void set(T value) {  
       Thread t = Thread.currentThread();  
       ThreadLocalMap map = getMap(t);  
       if (map != null)  
           map.set(this, value);  
       else  
           createMap(t, value);  
   } 

在這個(gè)方法內(nèi)部我們看到,首先通過getMap(Thread t)方法獲取一個(gè)和當(dāng)前線程相關(guān)的ThreadLocalMap,然后將變量的值設(shè)置到這個(gè)ThreadLocalMap對象中,當(dāng)然如果獲取到的ThreadLocalMap對象為空,就通過createMap方法創(chuàng)建。

==線程隔離的秘密,就在于ThreadLocalMap這個(gè)類。ThreadLocalMap是ThreadLocal類的一個(gè)靜態(tài)內(nèi)部類,它實(shí)現(xiàn)了鍵值對的設(shè)置和獲?。▽Ρ萂ap對象來理解),每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本,它所存儲的值,只能被當(dāng)前線程讀取和修改。ThreadLocal類通過操作每一個(gè)線程特有的ThreadLocalMap副本,從而實(shí)現(xiàn)了變量訪問在不同線程中的隔離。因?yàn)槊總€(gè)線程的變量都是自己特有的,完全不會有并發(fā)錯(cuò)誤。還有一點(diǎn)就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設(shè)置的對象了。== 這個(gè)就是實(shí)現(xiàn)原理

源碼實(shí)現(xiàn)片段:getMap、createMap

/** 
 * Get the map associated with a ThreadLocal. Overridden in 
 * InheritableThreadLocal. 
 * 
 * @param  t the current thread 
 * @return the map 
 */  
ThreadLocalMap getMap(Thread t) {  
    return t.threadLocals;  
}  
  
/** 
 * Create the map associated with a ThreadLocal. Overridden in 
 * InheritableThreadLocal. 
 * 
 * @param t the current thread 
 * @param firstValue value for the initial entry of the map 
 * @param map the map to store. 
 */  
void createMap(Thread t, T firstValue) {  
    t.threadLocals = new ThreadLocalMap(this, firstValue);  
}  

源碼實(shí)現(xiàn)片段:get

/** 
 * Returns the value in the current thread"s copy of this 
 * thread-local variable.  If the variable has no value for the 
 * current thread, it is first initialized to the value returned 
 * by an invocation of the {@link #initialValue} method. 
 * 
 * @return the current thread"s value of this thread-local 
 */  
public T get() {  
    Thread t = Thread.currentThread();  
    ThreadLocalMap map = getMap(t);  
    if (map != null) {  
        ThreadLocalMap.Entry e = map.getEntry(this);  
        if (e != null)  
            return (T)e.value;  
    }  
    return setInitialValue();  
}  

源碼實(shí)現(xiàn)片段:setInitialValue

/** 
    * Variant of set() to establish initialValue. Used instead 
    * of set() in case user has overridden the set() method. 
    * 
    * @return the initial value 
    */  
   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;  
   }  
   
   //獲取和當(dāng)前線程綁定的值時(shí),ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵
   //進(jìn)行查找的,這當(dāng)然和前面set()方法的代碼是相呼應(yīng)的。進(jìn)一步地,我們可以創(chuàng)建不同的
   //ThreadLocal實(shí)例來實(shí)現(xiàn)多個(gè)變量在不同線程間的訪問隔離,為什么可以這么做?因?yàn)椴?   //同的ThreadLocal對象作為不同鍵,當(dāng)然也可以在線程的ThreadLocalMap對象中設(shè)置不同
   //的值了。通過ThreadLocal對象,在多線程中共享一個(gè)值和多個(gè)值的區(qū)別,就像你在一個(gè)
   //HashMap對象中存儲一個(gè)鍵值對和多個(gè)鍵值對一樣,僅此而已。
四、ThreadLocal實(shí)際用途

例1:在數(shù)據(jù)庫管理中的連接管理類是下面這樣的:(摘自網(wǎng)上)

public class ConnectionManager {
    private static Connection connect = null;

    public static Connection getConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    ...

} 

在單線程的情況下這樣寫并沒有問題,但如果在多線程情況下回出現(xiàn)線程安全的問題。你可能會說用同步關(guān)鍵字或鎖來保障線程安全,這樣做當(dāng)然是可行的,但考慮到性能的問題所以這樣子做并是很優(yōu)雅。
下面是改造后的代碼:

public class ConnectionManager {

    private static ThreadLocal connThreadLocal = new ThreadLocal();

    public static Connection getConnection() {
        if(connThreadLocal.get() != null)
            return connThreadLocal.get();
        
        //獲取一個(gè)連接并設(shè)置到當(dāng)前線程變量中
        Connection conn = getConnection();
        connThreadLocal.set(conn);
        return conn;
    }
    
    ...

}

例2:日期格式(摘自網(wǎng)上)

使用這個(gè)日期格式類主要作用就是將枚舉對象轉(zhuǎn)成Map而map的值則是使用ThreadLocal存儲,那么在實(shí)際的開發(fā)中可以在同一線程中的不同方法中使用日期格式而無需在創(chuàng)建日期格式的實(shí)例。

public class DateFormatFactory {

    public enum DatePattern {

        TimePattern("yyyy-MM-dd HH:mm:ss"),
        DatePattern("yyyy-MM-dd");

        public String pattern;

        private DatePattern(String pattern) {
            this.pattern = pattern;
        }
    }

    private static final Map> pattern2ThreadLocal;

    static {
        DatePattern[] patterns = DatePattern.values();
        int len = patterns.length;
        pattern2ThreadLocal = new HashMap>(len);

        for (int i = 0; i < len; i++) {
            DatePattern datePattern = patterns[i];
            final String pattern = datePattern.pattern;

            pattern2ThreadLocal.put(datePattern, new ThreadLocal() {
                @Override
                protected DateFormat initialValue() {
                    return new SimpleDateFormat(pattern);
                }
            });
        }
    }

    //獲取DateFormat
    public static DateFormat getDateFormat(DatePattern pattern) {
        ThreadLocal threadDateFormat = pattern2ThreadLocal.get(pattern);
        //不需要判斷threadDateFormat是否為空
        return threadDateFormat.get();
    }

    public static void main(String[] args) {
         String dateStr = DateFormatFactory.getDateFormat(DatePattern.TimePattern).format(new Date());
         System.out.println(dateStr);
    }


}
五、總結(jié)

ThreadLocal是用冗余的方式換時(shí)間,而鎖機(jī)制則是時(shí)間換空間,好的設(shè)計(jì)往往都是在時(shí)間、空間以及復(fù)雜度之間做權(quán)衡,道理是這樣但是真正能平衡三者之間的人我姑且稱之為“大成者”,愿你我在成長的道路上越走越遠(yuǎn)。

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

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

相關(guān)文章

  • Java 總結(jié)

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

    caspar 評論0 收藏0
  • 七面阿里:現(xiàn)在分享一下阿里最全面試116題:阿里天貓、螞蟻金服、阿里巴巴面試題含答案

    摘要:面試,是跳槽后第一個(gè)需要面對的問題而且不同公司面試的著重點(diǎn)不同但是卻有一個(gè)共同點(diǎn)基礎(chǔ)是必考的。對自動(dòng)災(zāi)難恢復(fù)有要求的表。 貌似這一點(diǎn)適應(yīng)的行業(yè)最廣,但是我可以很肯定的說:當(dāng)你從事Java一年后,重新找工作時(shí),才會真實(shí)的感受到這句話。 工作第一年,往往是什么都充滿新鮮感,什么都學(xué)習(xí),沖勁十足的一年;WEB行業(yè)知識更新特別快,今天一個(gè)框架的新版本,明天又是另一個(gè)新框架,有時(shí)往往根據(jù)項(xiàng)目的需...

    animabear 評論0 收藏0
  • 七面阿里:現(xiàn)在分享一下阿里最全面試116題:阿里天貓、螞蟻金服、阿里巴巴面試題含答案

    摘要:面試,是跳槽后第一個(gè)需要面對的問題而且不同公司面試的著重點(diǎn)不同但是卻有一個(gè)共同點(diǎn)基礎(chǔ)是必考的。對自動(dòng)災(zāi)難恢復(fù)有要求的表。 貌似這一點(diǎn)適應(yīng)的行業(yè)最廣,但是我可以很肯定的說:當(dāng)你從事Java一年后,重新找工作時(shí),才會真實(shí)的感受到這句話。 工作第一年,往往是什么都充滿新鮮感,什么都學(xué)習(xí),沖勁十足的一年;WEB行業(yè)知識更新特別快,今天一個(gè)框架的新版本,明天又是另一個(gè)新框架,有時(shí)往往根據(jù)項(xiàng)目的需...

    fjcgreat 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<