學(xué)習(xí)ThreadLocal,必須知道的幾個數(shù)據(jù)類型:
1.Thread
2.ThreadLocalMap
3.Entry
4.ThreadLocal
5.WeakReference
使用ThreadLocal進(jìn)行資源隔離的注意事項:
initValue初始化的時候需要return new Object(); 讓每個對象持有不同的對象引用
ThreadLocal存在的問題:
1.在使用線程池時,由于線程池中的核心線程再使用完成后并不會被銷毀。這個時候如果前一個任務(wù)在ThreadLocal中set一個值, 那么后續(xù)任務(wù)可以訪問前一個任務(wù)在ThreadLocal中set的值,這樣就有可能到導(dǎo)致業(yè)務(wù)處理的時候會出現(xiàn)錯誤
2.假如ThreadLocal并沒有聲明為static,由于ThreadLocalMap中它是 由Entry數(shù)組構(gòu)成的,Entry
將ThreadLocal通過弱引用指向,就有可能出現(xiàn)發(fā)生GC時,Entry.get = null但是Value還有值的情況。這種情況也是內(nèi)存泄露的一種
3.ThreadLocal沒辦法解決共享變量的更新問題,存在并發(fā)安全問題(下面會有代碼給出)
ThreadLocal的使用建議
1.不管是使用到線程池還是沒有使用到線程池。在每個任務(wù)在執(zhí)行完業(yè)務(wù)處理后,需要手動的調(diào)用ThreadLocal.remove方法,清空之前set的數(shù)據(jù)
2.ThreadLocal聲明為static,避免出現(xiàn)內(nèi)存泄露的問題
3.ThreadLocal只做資源隔離和參數(shù)傳遞,沒辦法保證并發(fā)情況下的資源安全問題
資源隔離例子
package EveryTest.EveryThreadLocal;
/**
* ThreadLocal資源隔離
*
* @author wuhui
* @date 2020/05/08
*/
public class ThreadLocalTest {
private static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<StringBuilder>() {
@Override
protected StringBuilder initialValue() {
// 資源隔離,每個線程擁有自己的StringBuilder
return new StringBuilder("init");
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
StringBuilder sb = threadLocal.get();
for (int num = 0; num < 10; num++) {
sb.append("-").append(num);
}
System.out.println(Thread.currentThread().getName() + sb.toString());
}, "MyThread_" + i).start();
}
}
/* 輸出結(jié)果
* MyThread_0init-0-1-2-3-4-5-6-7-8-9
MyThread_3init-0-1-2-3-4-5-6-7-8-9
MyThread_2init-0-1-2-3-4-5-6-7-8-9
MyThread_4init-0-1-2-3-4-5-6-7-8-9
MyThread_1init-0-1-2-3-4-5-6-7-8-9
MyThread_5init-0-1-2-3-4-5-6-7-8-9
MyThread_6init-0-1-2-3-4-5-6-7-8-9
MyThread_7init-0-1-2-3-4-5-6-7-8-9
MyThread_8init-0-1-2-3-4-5-6-7-8-9
MyThread_9init-0-1-2-3-4-5-6-7-8-9
* */
}
ThreadLocal操作共享變量出現(xiàn)并發(fā)安全的問題的例子
package EveryTest.EveryThreadLocal;
import java.util.concurrent.CountDownLatch;
/**
* ThreadLocal沒辦法解決共享變量的更新問題,存在并發(fā)安全問題
*
* @author wuhui
* @date 2020/05/08
*/
public class ThreadLocalTest2 {
private static NumObject numObject = new NumObject(0);
private static ThreadLocal<NumObject> threadLocal = new ThreadLocal<NumObject>() {
@Override
protected NumObject initialValue() {
return numObject;
}
};
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(MyConst.THREAD_NUM);
for (int i = 1; i <= MyConst.THREAD_NUM; i++) {
new Thread(() -> {
NumObject numObject = threadLocal.get();
for (int num = 0; num < MyConst.EVERY_THREAD_INCR_TIMES; num++) {
numObject.incrementValue();
}
// 每個線程打印的結(jié)果需要是10的整數(shù)倍才正確
System.out.println(Thread.currentThread().getName() + ":" + numObject.getValue());
countDownLatch.countDown();
}, MyConst.THREAD_NAME_PREFIX + i).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 最后結(jié)果應(yīng)該要是THREAD_NUM * EVERY_THREAD_INCR_TIMES才對,如果不是 說明ThreadLocal并不能解決并發(fā)更新共享變量的問題
System.out.println(numObject.getValue());
}
}
class NumObject {
private int value;
public NumObject() {
}
public NumObject(int value) {
this.value = value;
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void incrementValue() {
this.value++;
}
public void decrementValue() {
this.value--;
}
@Override
public String toString() {
return "NumObject{" +
"value=" + value +
'}';
}
}
class MyConst {
public static final String THREAD_NAME_PREFIX = "MYTHREAD_";
/**
* 可通過調(diào)節(jié)線程個數(shù)驗證結(jié)果,放大線程數(shù)量增加出現(xiàn)并發(fā)更新共享變量問題出現(xiàn)的概率
*/
public static final int THREAD_NUM = 100;
public static final int EVERY_THREAD_INCR_TIMES = 1000;
private MyConst() {
}
}
測試了幾次 數(shù)據(jù)分別為:
98750
97558
98344
96238
96904
98921
從測試結(jié)果來看,都不是100*1000。由此得出結(jié)論,ThreadLocal確實(shí)存在并發(fā)更新的安全問題