基於OpenJDK 12html
本文主要想了解兩個地方:java
先看一下ThreadLocal的官方API解釋爲:git
該類提供了線程局部 (thread-local) 變量。這些變量不一樣於它們的普通對應物,由於訪問某個變量(經過其 get 或 set 方法)的每一個線程都有本身的局部變量,它獨立於變量的初始化副本[原文:These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.]。ThreadLocal 實例一般是類中的 private static 字段,它們但願將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。github
大概的意思有兩點:api
看一段代碼:bash
// 代碼來自:
// http://tutorials.jenkov.com/java-concurrency/threadlocal.html
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
@Override
public void run() {
//注意這裏 set的值是run函數的內部變量,若是是MyRunnable的全局變量
//則沒法起到線程隔離的做用
threadLocal.set((int) (Math.random() * 100D));
try {
//sleep兩秒的做用是讓thread2 set操做在thread1的輸出以前執行
//若是線程之間是共用threadLocal,則thread2 set操做會覆蓋掉thread1的set操做
//從而二者的輸出都是thread2 set的值
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); //wait for thread 1 to terminate
thread2.join(); //wait for thread 2 to terminate
}
}
複製代碼
輸出結果:微信
thread1 start
thread2 start
38
thread1 join
78
thread2 join
複製代碼
MyRunnable run中sleep兩秒的做用是讓thread2 set操做在thread1的輸出以前執行,若是線程之間是共用threadLocal,則thread2 set操做會覆蓋掉thread1的set操做,二者的輸出都是thread2 set的值,從而輸出的應該是同一個值。oracle
但從代碼執行結果來看,thread一、thread2的threadLocal是不一樣的,也就是實現了線程隔離。dom
看一眼ThreadLocal set方法:ide
public void set(T value) {
//currentThread是個native方法,會返回對當前執行線程對象的引用。
Thread t = Thread.currentThread();
//getMap 返回線程自身的threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
//把value set到線程自身的ThreadLocalMap中了
map.set(this, value);
} else {
//線程自身的ThreadLocalMap未初始化,則先初始化,再set
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread類中
//ThreadLocalMapset的set方法未執行深拷貝,須要注意傳遞值的類型
ThreadLocal.ThreadLocalMap threadLocals = null;
複製代碼
從代碼中能夠看到,在set的時候,會根據Thread對象的引用來將值添加到各自線程中。但set的值value仍是同一個對象,既然傳遞的是同一個對象,那就涉及到另外一個問題:參數值傳遞、引用傳遞的問題了。
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Object> threadLocal = new ThreadLocal<>();
// MyRunnable 全局變量
int random;
@Override
public void run() {
random = (int) (Math.random() * 100D);
threadLocal.set(random);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
System.out.println("thread1 start");
thread2.start();
System.out.println("thread2 start");
thread1.join(); //wait for thread 1 to terminate
System.out.println("thread1 join");
thread2.join(); //wait for thread 2 to terminate
System.out.println("thread2 join");
}
}
複製代碼
輸出結果:
thread1 start
thread2 start
//兩個值不一樣
16
thread1 join
75
thread2 join
複製代碼
從輸出能夠看出二者隔離了。
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Object> threadLocal = new ThreadLocal<>();
// MyRunnable 全局變量
Obj obj = new Obj();
@Override
public void run() {
obj.value = (int) (Math.random() * 100D);
threadLocal.set(obj);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(((Obj) threadLocal.get()).value);
}
class Obj {
int value;
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
System.out.println("thread1 start");
thread2.start();
System.out.println("thread2 start");
thread1.join(); //wait for thread 1 to terminate
System.out.println("thread1 join");
thread2.join(); //wait for thread 2 to terminate
System.out.println("thread2 join");
}
}
複製代碼
輸出結果:
thread1 start
thread2 start
//兩個值相同
36
36
thread1 join
thread2 join
複製代碼
從輸出結果來看,當set操做的值是MyRunnable的全局變量,而且是引用類型的時候,沒法起到隔離的做用。
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Object> threadLocal = new ThreadLocal<>();
//Obj obj = new Obj();
@Override
public void run() {
Obj obj = new Obj();
obj.value = (int) (Math.random() * 100D);
threadLocal.set(obj);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
System.out.println(((Obj) threadLocal.get()).value);
}
class Obj {
int value;
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
System.out.println("thread1 start");
thread2.start();
System.out.println("thread2 start");
thread1.join(); //wait for thread 1 to terminate
System.out.println("thread1 join");
thread2.join(); //wait for thread 2 to terminate
System.out.println("thread2 join");
}
}
複製代碼
輸出結果:
thread1 start
thread2 start
//兩個值不一樣
12
19
thread1 join
thread2 join
複製代碼
從輸出結果看,局部引用,能夠相互隔離。
到這裏能夠看出ThreadLocal,只是把set值或引用綁定到了當前線程,但卻沒有進行相應的深拷貝,因此ThreadLocal要想作的線程隔離,必須是基本類型或者run的局部變量。
看一下ThreadLocalMap內部Entry:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
複製代碼
從代碼中看到,Entry繼承了WeakReference,並將ThreadLocal設置爲了WeakReference,value設置爲強引用。也就是:當沒有強引用指向ThreadLocal變量時,它可被回收。
可是,還有一個問題:ThreadLocalMap維護ThreadLocal變量與具體實例的映射,當ThreadLocal變量被回收後,該映射的key變爲 null,而該Entry仍是在ThreadLocalMap中,從而這些沒法清理的Entry,會形成內存泄漏。
ThreadLocal自帶的remove、set方法,都沒法處理ThreadLocal自身爲null的狀況,由於代碼中都直接取ThreadLocal的threadLocalHashCode屬性了,因此若是ThreadLocal自身已是null,這時調用remove、set會報空指針異常(java.lang.NullPointerException)的。
因此,在使用ThreadLocal的時候,在使用完畢記得remove(remove方法會將Entry的value及Entry自身設置爲null並進行清理)。
JDK 12 ThreadLocal代碼地址: github.com/jiankunking…
我的微信公衆號:
我的github:
我的博客: