ThreadLocal 是線程的局部變量, 是每一個線程所單獨持有的,其他線程不能對其進行訪問
ThreadLocal<Date>
就是設置 value 為 Date 類型tl
的 tl.get()
方法時,其實就是先去取得此線程的 ThreadLocalMap,然後再去查找這個 Map 中的 key 為 tl
的那個 Entry 的 value 值ThreadLocal 常用的方法
set(x)
: 設置此線程的想要放的值是多少get()
: 取得此線程當初存放的值,如果沒有存放過則返回 nullremove()
: 刪除此線程的鍵值對,也就是如果先執行 remove 再執行 get,會返回 nullThreadLocal 通常用在 SimpleDateFormat,或是 SpringMVC 上
new
一個,但是這樣做會很浪費資源,所以如果使用 ThreadLocal 在每個線程裡都存放一個此線程專用的 SimpleDateFormat,就可以避免一直 new
的資源浪費,又確保線程安全ThreadLocal 可能造成的內存洩漏
在Java裡,每個線程都有自己的 ThreadLocalMap,裡面存著這個線程自己私有的 ThreadLocal 們,而 ThreadLocalMap 的 key 為 ThreadLocal 實例,value 為私有對象 T,即是透過 set()
設置的值
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
static class Entry extends WeakReference<ThreadLocal> {
Object value;
//ThreadLocalMap中的key就是ThreadLocal,value就是設置的值
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
}
}
可以創建許多個 ThreadLocal 對象,對每個 ThreadLocal 都設置不同的值
像是以下的例子,在 main 線程中的 ThreadLocalMap,就有兩個 key-value 的映射,分別是 userIdThreadLocal -> 100、userNameThreadLocal -> hello
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");
}
}
之所以 ThreadLocal 會發生內存洩漏,原因是因為只要線程活著,這個線程的 ThreadLocalMap 就會一直活著,而當初透過 ThreadLocal set()
的值,也就會在 ThreadLocalMap 中一直存在這個鍵值對不消失,所以該 ThreadLocal 和該 value 的內存地址始終都有這個 ThreadLocalMap 在引用著,導致 GC 無法回收他,所以才會發生內存洩漏
為了解決這個問題,java 做了一個小優化,也就是存放在 ThreadLocalMap 中的 ThreadLocal,會使用 弱引用 來儲存,也就是說,如果一個 ThreadLocal 內存地址沒有外部強引用來引用他,只有這條 ThreadLocalMap 的弱引用來引用他時,那麼當系統 GC 時,這些 ThreadLocal 就會被回收(因為是弱引用),如此一來,ThreadLocalMap 中就會出現 key 為 null 的 Entry 們
這個弱引用優化只能使得 ThreadLocal 被正確回收,但是這些 key 為 null 的 Entry 們仍然會存在在 ThreadLocalMap 裡,因此 value 仍然無法被回收
所以 java 又做了一個優化,就是在 ThreadLocal 執行 get()
、set()
、remove()
方法時,都會將該線程 ThreadLocalMap 裡所有 key = null 的 value 也設置為 null,手動幫助 GC
ThreadLocal k = e.get();
if (k == null) {
e.value = null; // Help the GC
}
但是根本上的解決辦法,還是在當前線程使用完這個 ThreadLocal 時,就即時的 remove()
掉該 value,也就是使得 ThreadLocalMap 中不要存在這個鍵值對,這樣才能確保 GC 能正確回收
具體實例
每個線程都可以在 ThreadLocal 中放自己的值,且不會干擾到其他線程的值
class Tools {
public static ThreadLocal threadLocal = new ThreadLocal();
}
class MyThread extends Thread {
@Override
public void run() {
if (Tools.threadLocal.get() == null) {
Tools.threadLocal.set(Thread.currentThread().getName() + ", " + Math.random());
}
System.out.println(Tools.threadLocal.get());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
MyThread thread = new MyThread();
thread.setName("thread " + i);
thread.start();
}
}
}
thread 1, 0.86
thread 0, 0.42
thread 2, 0.35
thread 3, 0.41
thread 4, 0.45
使用 ThreadLocal 在 SimpleDateFormat 上,並且給 ThreadLocal 加上泛型,指定 value 的類型是 SimpleDateFormat
因為使用了 ThreadLocal 確保每個線程有自己一份 SimpleDateFormat,所以線程安全,不會報錯
class Tools {
public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>();
}
class MyThread extends Thread {
@Override
public void run() {
SimpleDateFormat sdf = Tools.threadLocal.get();
if (sdf == null) {
sdf = new SimpleDateFormat("yyyy-MM-dd");
Tools.threadLocal.set(sdf);
}
try {
System.out.println(sdf.parse("2018-07-15"));
} catch (ParseException e) {
System.out.println("報錯了");
}
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
MyThread thread = new MyThread();
thread.setName("thread " + i);
thread.start();
}
}
}
Sun Jul 15 00:00:00 CST 2018
Sun Jul 15 00:00:00 CST 2018
Sun Jul 15 00:00:00 CST 2018
Sun Jul 15 00:00:00 CST 2018
Sun Jul 15 00:00:00 CST 2018
使用 ThreadLocal 在 SpringMVC上
攔截器 MyInterceptor 先去從 cookie 中取得當前用戶信息,透過 UserUtils 放到 ThreadLocal<User>
裡
然後當 MyController 要去取得這個請求(也就是這條線程)的用戶信息時,就去調用 UserUtils 取得放在 ThreadLocal<User>
裡面的 User 信息
最後當請求結束時,刪除此條線程的 ThreadLocal<User>
信息,避免內存洩漏
//UserUtils專門存取User信息
public class UserUtils {
public static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static User getUser() {
return userThreadLocal.get();
}
public static void removeUser() {
if (userThreadLocal.get() != null) {
userThreadLocal.remove();
}
}
}
//攔截器取得cookie中的User信息,並調用UserUtils放到ThreadLocal裡
//請求結束時要記得把ThreadLocal中的User刪除,因為這條線程之後還要去服務其他請求
public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle() throws Exception {
User user = getUserFromCookie();
UserUtils.setUser(user);
return true;
}
@Override
public void postHandle() throws Exception {
UserUtils.removeUser();
}
}
//MyContoller調用UserUtils取得ThreadLocal<User>中的User
@Controller
@RequestMapping("/")
public class MyController {
@RequestMapping("/")
public void test() {
User user = UserUtils.getUser();
System.out.println("User id: " + user.id);
}
}