定義
線程本地變量褐澎,也有些地方叫做線程本地存儲岩臣,其實意思差不多脚牍。ThreadLocal可以讓每個線程擁有一個屬于自己的變量的副本向臀,不會和其他線程的變量副本沖突,實現(xiàn)了線程的數(shù)據(jù)隔離诸狭。
使用示例
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
static class MyThread extends Thread{
@Override
public void run() {
super.run();
threadLocal.set("son");
System.out.println(threadLocal.get()); //son
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
threadLocal.set("main");
System.out.println(threadLocal.get()); //main
}
}
通過上面的代碼我們發(fā)現(xiàn)券膀,當(dāng)我我們使用同一個ThreadLocal對象在不同線程中set不同的值,會打印出不同的值驯遇,這是怎么做到的呢芹彬?上面定義中提到每個線程擁有一個屬于自己的變量的副本,這就能解通為什么我們可以獲取到不同的值叉庐。
既然每個線程有一個自己的變量副本舒帮,那我們自己是不是也可以實現(xiàn)呢?答案是肯定的陡叠,下面我們自己實現(xiàn)一個類似的功能玩郊。
//自定義實現(xiàn)的ThreadLocal
public class MyThreadLocal<T> {
private Map<Thread, T> hashMap = new HashMap();
//多線程下保證原子性
public synchronized void set(T t) {
synchronized (MyThreadLocal.this) {
hashMap.put(Thread.currentThread(), t);
}
}
public synchronized T get() {
return hashMap.get(Thread.currentThread());
}
}
public class ThreadLocalTest {
//使用我們自己定義的ThreadLocal
private static MyThreadLocal<String> threadLocal = new MyThreadLocal<>();
static class MyThread extends Thread{
@Override
public void run() {
super.run();
threadLocal.set("son");
System.out.println(threadLocal.get()); //son
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
threadLocal.set("main");
System.out.println(threadLocal.get()); //main
}
}
我們自己實現(xiàn)了一個MyThreadLocal對象,創(chuàng)建了一個Key是Thread的Map對象存儲對應(yīng)線程的數(shù)據(jù)枉阵,也實現(xiàn)了ThreadLocal的功能译红,下面我們看看系統(tǒng)的ThreadLocal是怎么做的。
源碼分析
我們從set方法開始分析兴溜。
public void set(T value) {
// 獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
if (map != null) //不為空直接調(diào)用ThreadLocalMap的set方法
map.set(this, value);
else
createMap(t, value); //為空創(chuàng)建ThreadLocalMap 并將數(shù)據(jù)存儲起來
}
然后跟進getMap方法
ThreadLocalMap getMap(Thread t) {
//這里調(diào)用到了Thread中侦厚,也就是說ThreadLocalMap對象是從Thread中獲取的
return t.threadLocals;
}
繼續(xù)看看Thread的threadLocals對象
//Thread中定義的threadLocals屬性
ThreadLocal.ThreadLocalMap threadLocals = null;
繼續(xù)看看ThreadLocalMap的set方法
private void set(ThreadLocal<?> key, Object value) {
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();
//如果存在key則更新值
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//沒有對應(yīng)的key則添加到數(shù)組中
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
上面我們看到反璃,存值的時候?qū)hreadLocal和Object(我們存儲的數(shù)據(jù))封裝到了一個Entry對象中,下面我們看下這個Entry
//ThreadLocalMap中的內(nèi)部類 存儲了ThreadLocal和我們保存的值
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
//ThreadLocal作為key
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
這里我們可以總結(jié)下大致的流程
1假夺、ThreadLocal調(diào)用set方法淮蜈,然后獲取當(dāng)前調(diào)用的Thread
2、根據(jù)Thread獲取threadLocals屬性(ThreadLocalMap)
3已卷、調(diào)用ThreadLocalMap的set方法
4梧田、將ThreadLocal和我們保存的值封裝成Entry對象,添加到數(shù)組(table)中
我們知道了存儲的大致流程侧蘸,獲取其實也是一樣的裁眯,都是對應(yīng)的對象調(diào)用流程,我們就不介紹了讳癌。
我們上面也實現(xiàn)了自己的MyThreadLocal對象穿稳,也完成了線程副本數(shù)據(jù)的存儲,我們思考下晌坤,這兩種方式有什么區(qū)別呢逢艘?系統(tǒng)為什么要做這么多操作步驟去實現(xiàn)線程副本數(shù)據(jù)呢?
我們自己的實現(xiàn)方式是創(chuàng)建了一個Map去存儲骤菠,當(dāng)多線程情況下它改,為了保證原子性,我們加了鎖商乎。
如果當(dāng)前有很多線程要獲取數(shù)據(jù)央拖,那么這些線程都會去爭奪這個map,沒有拿到的就會阻塞鹉戚,這樣性能上肯定是會有影響的鲜戒。
系統(tǒng)的實現(xiàn)方式則是,沒個線程都有自己的map抹凳,不存在并發(fā)的問題遏餐,自己用自己的,效率無疑是要高的却桶,所以系統(tǒng)的實現(xiàn)還是有一定道理的境输。
這就跟打籃球是一樣的,大家都搶一個球颖系,和每個人都有一個球嗅剖,效果肯定是不一樣的。