ThreadLocal 線程數據共用和安全 1.什麼是ThreadLocal? ThreadLocal的作用,可以實現在同一個線程數據共用,從而解決多線程數據安全問題 當http請求發送到Tomcat服務端時,Tomcat會創建一個線程去處理這個http請求,如果是請求servlet,servlet ...
ThreadLocal
線程數據共用和安全
1.什麼是ThreadLocal?
-
ThreadLocal的作用,可以實現在同一個線程數據共用,從而解決多線程數據安全問題
當http請求發送到Tomcat服務端時,Tomcat會創建一個線程去處理這個http請求,如果是請求servlet,servlet可能又會調用其他service,在這些service中,又可能會調用dao,去對資料庫進行操作。
在這些資源或者方法的調用中,為解決數據安全問題,在這一個線程執行的過程中,我們希望有一個數據是共用的,而且是安全的。
應用場景:比如說事務控制,一個線程可能涉及到多個service的調用,調用多個dao,在這過程中,可能對資料庫的多張表進行了操作。這時我們希望在整個業務流程結束之後,再進行一次提交commit。反過來說,在沒有進行提交之前,我們希望始終是一個connection在操作,這樣才能在結束時進行統一的一次提交(在開始操作的時候將自動提交設置為false)。
這樣就可以解決同一個請求中,調用多個service或者多個dao的需求,這個需求也是開發中必須解決的事務安全問題(事務一致性需求)。
ThreadLocal技術就能夠很好地解決這個問題。我們可以在實際開發中使用Filter和ThreadLocal解決事務安全問題。
-
一個ThreadLocal對象可以給當前線程關聯一個數據(普通變數,對象,對組)--使用set方法
-
ThreadLocal可以像Map一樣存取數據,key為當前的ThreadLocal對象--使用get方法
-
每一個ThreadLocal對象只能為當前線程關聯一個數據,如果要為當前線程關聯多個數據,就需要使用多個ThreadLocal對象實例
-
每個ThreadLocal對象實例定義的時候,一般為static類型
-
ThreadLocal中保存的數據,線上程銷毀之後,會自動釋放
2.ThreadLocal快速入門
2.1ThreadLocal的類圖
如下:ThreadLocal類中常用的方法有get(),set(),getMap()等,ThreadLocal類中含有一個重要的內部類ThreadLocalMap,ThreadLocalMap類中又含有一個內部類Entry,數據以key-value的形式存放在Entry中。
- ThreadLocal核心的價值就是:在一個線程中,以線程安全的方式來共用數據。
2.2應用實例
需求: 演示 ThreadLocal (作用:在一個線程中, 共用數據(線程安全))的使用
Dog:
package com.li.threadlocal;
public class Dog {
}
T1:
package com.li.threadlocal;
public class T1 {
//創建ThreadLocal對象, public static修飾
public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Task()).start();//啟動一個新的線程,註意不是主線程
}
//Task是一個線程類,同時是一個內部類
public static class Task implements Runnable {
@Override
public void run() {
Dog dog = new Dog();
Pig pig = new Pig();
//給threadLocal1對象放入dog
System.out.println("Task 放入了 dog=" + dog);
threadLocal1.set(dog);
System.out.println("Task 的 run 方法中的線程= " + Thread.currentThread().getName());
new T1Service().update();
}
}
}
T1Service:
package com.li.threadlocal;
public class T1Service {
public void update() {
//取出threadLocal1對象關聯的對象
Object o = T1.threadLocal1.get();
//獲取當前線程名
String name = Thread.currentThread().getName();
System.out.println("在T1Service 的update()的線程是= " + name + ", 取出dog= " + o);
//調用了dao-update()方法
new T1DAO().update();
}
}
T1DAO:
package com.li.threadlocal;
public class T1DAO {
public void update() {
//取出線程關聯的threadLocal1對象的數據
Object o = T1.threadLocal1.get();
//獲取當前線程的名稱
String name = Thread.currentThread().getName();
System.out.println("在T1DAO 的update()的線程是= " + name + ", 取出dog= " + o);
}
}
可以看到所有方法中拿到的對象都是同一個:
3.源碼分析
3.1ThreadLocal的set()
在2.2的應用實例中,我們在T1類中使用了ThreadLocal.set()方法,現在來看看set()方法的底層源碼:
set()方法關聯的其他方法和屬性:
從上述代碼中我們可以知道set方法的主要工作如下:
public void set(T value) {
//1.獲取當前線程
Thread t = Thread.currentThread();
//2.通過線程對象,獲取到和此線程關聯的ThreadLocalMap
// (ThreadLocalMap是ThreadLocal里的一個靜態內部類*,
// 類型是ThreadLocal$ThreadLocalMap)
ThreadLocalMap map = getMap(t);
//3.如果獲取到的ThreadLocalMap不為空,就將傳入的數據放入map,其中:
// -key為當前的ThreadLocal對象(this) -value為存放的數據,
// 因為key值不能重覆(map性質),一個ThreadLocal對象只能存放一個數據
// 如果再賦值,就會替換舊的value值
if (map != null)
map.set(this, value);
//4.如果和當前線程關聯的ThreadLocalMap為null,
//就創建一個和當前線程關聯的ThreadLocalMap,並且將存放的數據作為value放入map,
//這裡的key為當前線程t(作用是讓線程和創建的map關聯起來)
else
createMap(t, value);
}
在2.2應用實例的threadLocal1.set(dog);
語句旁打上斷點,點擊debug,點擊step over。
如下圖所示,可以看到子線程Thread-0中有一個threadLocals屬性(該屬性的類型為ThreadLocalMap),該map中又有一個table屬性(table的類型為Entry[]數組), table數組存放Entry對。
這裡涉及到的弱引用暫不深入
在Entry對中,以k-v的形式存放數據,key值為 當前的線程中 的ThreadLocal對象,value值為存放的數據。
因為map的存放性質,如果在同一個ThreadLocal對象中存放多個value,那麼在底層的Entry對中保存的是最近存放的value值,這也是為什麼一個ThreadLocal對象只能存放一個值。
線程中所有的ThreadLocal對象都被當前線程的threadLocals屬性(map)管理。因此無論在哪個方法中,只要能找到對應的線程Thread,就對該線程關聯的所有ThreadLocal對象中的value值進行操作。
一個線程中可以有多個ThreadLocal對象,如果還有其他ThreadLocal對象,使用set方法,存放的就是其他Entry對(key值就是其他的ThreadLocal對象)
存放多個ThreadLocal對象:
3.2ThreadLocal的get()
public T get() {
//先得到當前的線程對象
Thread t = Thread.currentThread();
//獲取和當前線程對象關聯的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果map不為空
if (map != null) {
//就根據當前的調用者this(即當前調用get方法的ThreadLocal對象),得到對應的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
//如果Entry的值e不為空
if (e != null) {
@SuppressWarnings("unchecked")
//返回當前ThreadLocal對象關聯的value值
T result = (T)e.value;
return result;
}
}
//如果map為空,就初始化map,並將map和當前線程關聯
return setInitialValue();
}