线程的局部变量ThreadLocal概念
ThreadLocal是什么
對這個詞語分解,將其分為Thread和Local,顧名思義便是本線程的變量,既然是當前線程的變量,那么就意味著這個變量對于其他線程來說就是隔離的,也就是不可見的,ThreadLocal對每一個線程都有一個副本,以確保每一個線程都能且只能訪問自己線程內部的副本變量
ThreadLocal如何實現
聽起來這個ThreadLocal是不是也是比較容易懂的,那么嗎,ThreadLocal又是怎么實現的呢?
在最早期,ThreadLocal的實現方式就是利用類內部的一個線程安全的Map,這個Map的鍵是線程的ID,值是實例的對象。這樣就使得線程之間互不干擾,能起到隔離的效果,但這樣做會有一個缺點:
多線程的情況之下,多個線程會競爭同一個map的鍵,競爭產生了,那么就得進行加鎖處理,這樣會耗費比較多的資源,除此之外,這種方式也可能造成內存的泄露問題。
而為了解決這些缺點,我們可以有另一種方式實現ThreadLocal,可以簡單的理解為上述方式“反過來”,線程對象內部存放一個Map,而以ThreadLocal作為Map的鍵。這樣的話就能避免競爭問題,而這樣,每個線程訪問的都是自己內部的map,這也是現在的ThreadLocal使用的實現方式。
ThreadLocal舉例
先舉一個簡單的例子吧,這樣能使后面的源碼分析起來更加輕松一點
ThreadLocal既然是線程變量,那自然,在單線程的程序中是沒有什么用處的,舉一個多線程的例子:
package com.hhw;
public class ThreadLocalDemo {
? ? public static void main(String[] args) {
? ? ? ? final ThreadLocalT th = new ThreadLocalT();
? ? ? ? Thread t1 = new Thread("t1"){
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? th.set();
? ? ? ? ? ? ? ? System.out.println(th.get());
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread t2 = new Thread("t2"){
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? System.out.println(th.get());
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t1.start();
? ? ? ? t2.start();
? ? }
}
class ThreadLocalT{
? ? ThreadLocal<Long> threadId = new ThreadLocal<Long>();
? ? public void set(){
? ? ? ? threadId.set(Thread.currentThread().getId());
? ? }
? ? public Long get(){
? ? ? ? return threadId.get();
? ? }
}
結果如下:
null
Process finished with exit code 0
第一個線程,set了其id,為線程id,第二個線程為set其id。
當用get分別取出兩個線程的id時,第一個線程是有值的,而第二個線程是null
這便足以闡述ThreadLocal的線程隔離,即對不同的線程有不同的副本儲存
下面對照例子來分析ThreadLocal的源碼
ThreadLocal簡單源碼分析
1、構造方法
public ThreadLocal() {
? ? }
首先便是構造方法,ThreadLocal的構造方法只有一個無參方法,且是個空函數
表示僅僅創(chuàng)建一個本地變量
也即上述例子之中的
ThreadLocal<Long> threadId = new ThreadLocal<Long>();
1
2、get()/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);
? ? }
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()方法,三步走:
1、首先獲得當前的線程
2、再將當前線程調用getMap()方法返回一個map,類型為ThreadLocalMap
3、最后判斷map是否為空,如果不是就把map的值設置為參數,否則調用createMap
然后看一下get()方法,一樣是三步:
1,2步和set()一樣,都是先獲取線程,然后調用getMap()方法,返回一個ThreadLocalMap類型的map對象
3、最后判斷一下map是否為空,如果不為空就獲取當前的ThreadLocal鍵值對應的entry,并返回value
如果為空就直接調用setIntialValue()并返回
3、ThreadLocalMap
發(fā)現,set()/get()方法中均出現有ThreadLocalMap這一個類,這是ThreadLocal的一個內部類,查看一下該類的源碼可以發(fā)現:這個類嚴格意義上并不能算是Map,它并沒有實現Map接口
它的底層是一個Entry類型的數組,
Entry源碼:
static class Entry extends WeakReference<ThreadLocal<?>> {
? ? ? ? ? ? /** The value associated with this ThreadLocal. */
? ? ? ? ? ? Object value;
? ? ? ? ? ? Entry(ThreadLocal<?> k, Object v) {
? ? ? ? ? ? ? ? super(k);
? ? ? ? ? ? ? ? value = v;
? ? ? ? ? ? }
? ? ? ? }
可見,它是一個繼承了一個弱引用(方便GC)的結構,其中將ThreadLocal作為鍵,一個Object類型的v作為值
4、remove()
除了get()和set()方法外,ThreadLocal還有一個比較重要的方法就是remove()了,這個方法能幫助我們對內存進行一定的管理,從而防止恐怖的內存泄露的發(fā)生
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;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
簡單來說就是刪除ThreadLocal中的Entry對象。
ThreadLocal使用場景
說了這么多,實際應用當中的ThreadLocal能給我們幫什么忙呢
1、參數的傳遞避免
想象一個場景,不同的學生之間,會有不同的成績,當多級(老師,教務等)之間都要查詢學生成績時,每次都要查詢一個學生的各科成績,會比較麻煩,但ThreadLocal就可以將學生的所有成績儲存在其中,并且不會與其他學生混淆,這樣就更加方便了。
2、管理數據庫連接池
我們可以將數據庫連接池交給ThreadLocal進行管理,使得統一線程在任意時候進行的操作都是使用一個連接,保證了事務。
?
總結
以上是生活随笔為你收集整理的线程的局部变量ThreadLocal概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决:org.apache.rocket
- 下一篇: 工具类:获取 spring 容器中 be