有關在實際工作上使用到 ThreadLocal 的例子,可以參考我的另一篇文章 Java - ThreadLocal 實際應用
ThreadLocal 從字面義直翻,就是執行緒 (Thread) 的局部變數,是每一個執行緒所單獨持有,其他執行緒不能對其進行存取
ThreadLocal 支持泛型,也就是支持 value 是可以設置的,像是 ThreadLocal<Integer>
就是設置 value 為 Integer 類型
每個執行緒會有自己一份 ThreadLocalMap 副本,去儲存這個執行緒自己想存放的 ThreadLocal<T>
變數們,ThreadLocalMap 副本內部儲存的是一個 key-value 對,其中 key 是某個 ThreadLocal<T>
物件實例 , value 就是這個執行緒、該 ThreadLocal<T>
物件實例 set 的值,所以對一個執行緒來說,一個 ThreadLocal<T>
只能存一個值,而一個執行緒可以存放多個 ThrealLocal<T>
public class Thread implements Runnable {
// Thread 類裡的threadLocals 存放此執行緒的專有 ThreadLocalMap 副本
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
// 根據執行緒,取得那個執行緒自己的 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
static class ThreadLocalMap {
// ThreadLocalMap 的 key 是使用 "弱引用" 的 ThreadLocal<T>
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
// ThreadLocalMap 中的 key 就是 ThreadLocal<T>,value 就是設置的值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
public T get() {
// 取得當前執行緒
Thread t = Thread.currentThread();
// 每個執行緒 都有一個自己的 ThreadLocalMap
// ThreadLocalMap 裡就保存著所有的ThreadLocal<T>變數
ThreadLocalMap map = getMap(t);
if (map != null) {
// ThreadLocalMap 的 key 就是當前 ThreadLocal<T> 物件實例
// 多個 ThreadLocal<T> 變數都是放在這個 map 中的
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 從 map 裡取出來的值就是我們需要的這個 ThreadLocal<T> 變數
T result = (T)e.value;
return result;
}
}
// 如果 map 沒有初始化,那麼在這裡初始化一下
return setInitialValue();
}
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 void remove() {
ThreadLocalMap map = getMap(Thread.currentThread());
if (map != null) {
map.remove(this);
}
}
}
可以創建多個 ThreadLocal<T>
物件,對每個 ThreadLocal<T>
都設置不同的值
public class Main {
public static void main(String[] args){
ThreadLocal<Integer> userIdThreadLocal = new ThreadLocal<>();
ThreadLocal<String> userNameThreacLocal = new ThreadLocal<>();
userId.set(100);
userName.set("hello");
}
}
這邊不多去探討這個議題,內存洩漏是可以避免的,只要當前執行緒要結束前記得即時的remove()
,也就是是使得 ThreadLocalMap 中不要存在這個 key-value 對,這樣才能確保 GC 能正確回收
以下有更多的文章,仔細地談論 ThreadLocal 內存洩漏問題 與 Java 自身解決的方式,但都不治本!還是當前執行緒用完 ThreadLocal 記得呼叫remove()
,才是確保線程安全的根本之道!
參考資料:
由於 SimpleDateFormat 本身非 synchronized ,所以如果在不同執行緒使用同一個 SimpleDateFormat ,就會導致輸出的時間異常,請看以下的例子
public static void main(String[] args) {
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date1 = new Date(1614820308016L);
Date date2 = new Date(1615820309016L);
System.out.println("初始定義時間1 : " + sdf.format(date1));
System.out.println("初始定義時間2 : " + sdf.format(date2));
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
String dateStr = sdf.format(date1);
if (!"2021-03-04 09:11:48".equals(dateStr)) {
System.out.println(Thread.currentThread().getName() + "異常時間為 : " + dateStr);
}
}
}, "thread 1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
String dateStr = sdf.format(date2);
if (!"2021-03-15 22:58:29".equals(dateStr)) {
System.out.println(Thread.currentThread().getName() + "異常時間為 : " + dateStr);
}
}
}, "Thread 2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
輸出異常:(Thread2 輸出了 Thread1 的時間)
初始定義時間1 : 2021-03-04 09:11:48
初始定義時間2 : 2021-03-15 22:58:29
Thread 2異常時間為 : 2021-03-04 09:11:48
但是, ThreadLocal 可以解決非線程安全的問題,只要在各自的執行緒,持有各自的ThreadLocal<DateFormat>
,就不會有問題了,請看下面的例子
public static void main(String[] args) {
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date1 = new Date(1614820308016L);
Date date2 = new Date(1615820309016L);
System.out.println("初始定義時間1 : " + sdf.format(date1));
System.out.println("初始定義時間2 : " + sdf.format(date2));
Thread t1 = new Thread(() -> {
ThreadLocal<DateFormat> currentSDF = new ThreadLocal<>();
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
currentSDF.set(df1);
DateFormat dateFormat1 = currentSDF.get();
for (int i = 0; i < 100; i++) {
String dateStr = dateFormat1.format(date1);
if (!"2021-03-04 09:11:48".equals(dateStr)) {
System.out.println(Thread.currentThread().getName() + "異常時間為 : " + dateStr);
}
}
currentSDF.remove();
}, "thread 1");
Thread t2 = new Thread(() -> {
ThreadLocal<DateFormat> currentSDF = new ThreadLocal<>();
DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
currentSDF.set(df2);
DateFormat dateFormat2 = currentSDF.get();
for (int i = 0; i < 100; i++) {
String dateStr = dateFormat2.format(date2);
if (!"2021-03-15 22:58:29".equals(dateStr)) {
System.out.println(Thread.currentThread().getName() + "異常時間為 : " + dateStr);
}
}
currentSDF.remove();
}, "Thread 2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
輸出正常:
初始定義時間1 : 2021-03-04 09:11:48
初始定義時間2 : 2021-03-15 22:58:29