自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ThreadLocal的使用及實(shí)現(xiàn)原理

開發(fā) 前端
ThreadLocal可以用來把實(shí)例變量共享成全局變量,讓程序中所有的方法都可以訪問到該變量。

前言

ThreadLocal直譯是本地線程,但實(shí)際上它的譯名是線程局部變量(ThreadLocalVariable)。ThreadLocal誕生的目的是隔離不同線程所使用的變量,官方對(duì)它的解釋是:

提供了線程局部變量,是獨(dú)立于變量的初始化副本”,也就是說它可以實(shí)現(xiàn)將某一個(gè)變量隔離在某個(gè)線程內(nèi),其它的線程無法訪問和使用這個(gè)變量。

我們先來做一個(gè)測(cè)試,先不使用ThreadLocal,創(chuàng)建三個(gè)線程:

public class ThreadLocalTest {
public static int num = 0;
public static int numAdd() {
return num++;
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(new MyRunnable());
t1.start();
t2.start();
t3.start();
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "-" + ThreadLocalTest.numAdd());
}
}

}
}

執(zhí)行后發(fā)現(xiàn)控制臺(tái)輸出的是:

可以發(fā)現(xiàn)線程執(zhí)行了numAdd()方法,從0-8跑了九次,num從0加到8,也就是說線程之間共享了靜態(tài)變量,從而導(dǎo)致線程的不安全問題。

然后我們?cè)偈褂肨hreadLocal來進(jìn)行測(cè)試。

public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static int numAdd() {
threadLocal.set(threadLocal.get()+1);
return threadLocal.get();
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(new MyRunnable());
t1.start();
t2.start();
t3.start();
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "-" + numAdd());
}
}

}
}

這里的numAdd方法使用了ThreadLocal的get()方法,這個(gè)方法調(diào)用了initialValue()方法并設(shè)置了返回值為0,通過調(diào)用這個(gè)方法+1,達(dá)到了num++的效果,這時(shí)候再看輸出的結(jié)果。

可以看到,三個(gè)不同的線程間相互隔離,變量的取值互不相干,也就是說ThreadLocal使用了不相干的變量,或者說ThreadLocal為每一個(gè)線程準(zhǔn)備了一個(gè)變量副本,那么它是如何實(shí)現(xiàn)的呢,我們點(diǎn)進(jìn)ThreadLocal的源碼看看。

這就是ThreadLocal的構(gòu)成了,主要操作是get()和set()方法:

get() : 返回當(dāng)前ThreadLocal的值

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

set() : 將當(dāng)前線程對(duì)象的值存入ThreadLocalMap中

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

首先創(chuàng)建了當(dāng)前Thread的對(duì)象,然后存入ThreadLocalMap中,對(duì)map進(jìn)行判斷,不為空就將this(當(dāng)前Threadlocal對(duì)象)存入作為key,并獲取對(duì)應(yīng)的值,最后是調(diào)用了一個(gè)setInitialValue()方法去獲得初始化的值。

ThreadLocalMap

介紹上面兩個(gè)方法主要是是為了引出ThreadLocal的實(shí)現(xiàn)原理,即ThreadLocalMap的創(chuàng)建和使用。

官方注釋中解釋道,ThreadLocalMap是一個(gè)定制的哈希映射,只適用于維護(hù)ThreadLocal的值。在ThreadLocal類之外沒有導(dǎo)出操作。類是包私有的,以允許在類線程中聲明字段。為了幫助處理非常大且長(zhǎng)期使用的用法,哈希表?xiàng)l目對(duì)鍵使用弱引用。

但是,由于不使用引用隊(duì)列,只有當(dāng)表開始耗盡空間時(shí),才開始刪除陳舊的條目。

點(diǎn)開ThreadLocalMap,可以看到一開始ThreadLocalMap定義了一個(gè)用于存儲(chǔ)數(shù)據(jù)的Entry 類。

 static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

這個(gè)Entry類繼承了弱引用類,眾所周知Java有四種引用類型,其中弱引用就是每次JVM進(jìn)行垃圾回收時(shí),都會(huì)回收該對(duì)象,保證了ThreadLocal每次拷貝當(dāng)前線程的值的時(shí)候所占的空間能被重新使用。

由get()方法可以得知,ThreadLocalMap的鍵(key)是ThreadLocal類的實(shí)例對(duì)象,value為用戶的值。

那么ThreadLocalMap的引用是在哪里呢,在上面的set()方法里,調(diào)用了getMap()和createMap()方法。

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

可以看到這邊調(diào)用了一個(gè)叫threadLocals的屬性,點(diǎn)擊這個(gè)屬性發(fā)現(xiàn)跳到了Thread類中。

  /* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

所以這個(gè)屬性便是ThreadLocalMap的引用了,那么ThreadLocal的實(shí)現(xiàn)原理也就很清晰了:

  1. 定義了一個(gè)ThreadLocalMap內(nèi)部類,使用的是Map的鍵值對(duì)方式來存取數(shù)據(jù),key是ThreadLocal類的實(shí)例對(duì)象,value為傳值。
  2. 創(chuàng)建新的ThreadLocal對(duì)象,調(diào)用set()或get()方法時(shí),也就是調(diào)用了ThreadLocalMap來進(jìn)行操作。
  3. 使用ThreadLocal時(shí),線程所使用的變量是獨(dú)享的(私有的變量副本),其他線程無法訪問,在使用過后(線程結(jié)束),這些變量會(huì)被GC回收。

使用ThreadLocal的原因

ThreadLocal可以用來把實(shí)例變量共享成全局變量,讓程序中所有的方法都可以訪問到該變量。

由于存到ThreadLocal的變量都是當(dāng)前線程本身,其他線程無法訪問,存到ThreadLocal中只是為了方便在程序中同一個(gè)線程之間傳遞這個(gè)變量(和解決線程安全沒有關(guān)系)。

責(zé)任編輯:姜華 來源: 今日頭條
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)