Java多線程程式設計-ThreadLocal和InheritableThreadLocal使用及其源碼分析
- ThreadLocal使用
-
- ThreadLocal相關API
-
- get()方法和set()方法
- initialValue()
- remove()
- InheritableThreadLocal類
如果我們想為每一個線程都有自己的共享變量,那我們可以使用ThreadLocal,如果我們使用public static是所有線程都可以使用。
ThreadLocal使用
提供線程局部變量。這些變量與普通變量不同,因為每次通路一個線程(通過其get或set方法)的線程都有其自己的,獨立初始化的變量副本。ThreadLocal執行個體通常是希望将狀态與線程關聯的類中的私有靜态字段
隻要線程是活動的并且ThreadLocal執行個體是可通路的,則每個線程都對其線程局部變量的副本持有隐式引用。線程消失後,其線程本地執行個體的所有副本都将進行垃圾回收(除非存在對這些副本的其他引用)
ThreadLocal相關API
get()方法和set()方法
get()方法:傳回該線程局部變量在目前線程副本中的值
public T get() {
//擷取目前線程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
set()方法:将此線程局部變量的目前線程副本設定為指定值
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 class ThreadLocalV1Main {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
if (threadLocal.get() == null) {
System.out.println("current thread the threadlocal is null " + Thread.currentThread().getName());
threadLocal.set("set value :" + Thread.currentThread().getName());
}
System.out.println("value is " + threadLocal.get());
}
}
運作結果如下:

我們可以通過set和get方法源碼可以看到,擷取目前對象中值,最終會存儲到一個Map(ThreadLocalMap:ThreadLocal的靜态内部類)中,這個是以線程為key,儲存相應的值。是以可以通過線程擷取值。是以每個線程中的值是每個線程特有的。
我們現在驗證一下ThreadLocal在多線程下每個線程之間獨立儲存一份
對象類:
public class ThreadLocalObj {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
}
線程1:
public class ThreadLocalT1 extends Thread {
public ThreadLocalT1(String name) {
this.setName(name);
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
System.out.println("current thread set thread local value,current thread name:" + Thread.currentThread().getName() + "and i = " + i);
ThreadLocalObj.threadLocal.set("current thread name:" + Thread.currentThread().getName());
System.out.println("current get thread local value:" + ThreadLocalObj.threadLocal.get());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
線程2:
public class ThreadLocalT2 extends Thread {
public ThreadLocalT2(String name) {
this.setName(name);
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
System.out.println("current thread set thread local value,current thread name:" + Thread.currentThread().getName() + "and i = " + i);
ThreadLocalObj.threadLocal.set("current thread name:" + Thread.currentThread().getName());
System.out.println("current get thread local value:" + ThreadLocalObj.threadLocal.get());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運作:
public class ThreadLocalV2Main {
public static void main(String[] args) throws InterruptedException {
ThreadLocalT1 threadLocalT1 = new ThreadLocalT1("thread01");
threadLocalT1.start();
ThreadLocalT2 threadLocalT2 = new ThreadLocalT2("thread02");
threadLocalT2.start();
for (int i = 0; i < 100; i++) {
System.out.println("current thread set thread local value,current thread name:" + Thread.currentThread().getName() + "and i = " + i);
ThreadLocalObj.threadLocal.set("current thread name:" + Thread.currentThread().getName());
System.out.println("current get thread local value:" + ThreadLocalObj.threadLocal.get());
Thread.sleep(1000);
}
}
}
運作結果:
有運作結果可以得出,三個線程(thread01,thread02和Main)都是取各自線程中的值。
initialValue()
我們上面使用get方法有可能為null,但是想ThreadLocal初始值不為null怎麼處理?就可以使用這個方法了。
initialValue()方法:傳回目前線程的這個線程局部變量的“初始值”
protected T initialValue() {
return null;
}
這個方法是一個protected ,我們通過子類從寫一下這個方法。
public class SubThreadLocal extends ThreadLocal<String> {
@Override
protected String initialValue() {
return "default value";
}
}
運作類:
public class SubThreadLocalMain {
public static SubThreadLocal threadLocal = new SubThreadLocal();
public static void main(String[] args) {
System.out.println(threadLocal.get());
}
}
運作結果:
表明ThreadLocal設定成功,ThreadLocal提供了一個私有的setInitialValue供get方法使用
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
這裡隻也是通過initialValue方法設定的。
remove()
上面有設定和擷取方法,作為儲存在map中的對象,肯定是有remove方法,不然不讓我删除多尴尬!
remove():删除此線程局部變量的目前線程值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
我們使用看看:
public class SubThreadLocalMain {
public static SubThreadLocal threadLocal = new SubThreadLocal();
public static void main(String[] args) {
System.out.println(threadLocal.get());
threadLocal.set("this is a set value");
System.out.println(threadLocal.get());
threadLocal.remove();
System.out.println(threadLocal.get());
}
}
運作結果:
輸出結果的先輸出初始值,後面輸出設定的值,在進行remove操作,在調用get擷取的為預設值。
InheritableThreadLocal類
此類擴充了ThreadLocal來提供從父線程到子線程的值的繼承:建立子線程時,子級将接收父級具有值的所有可繼承線程局部變量的初始值。 通常,子級的價值觀将與父級的價值觀相同。 但是,通過覆寫此類中的childValue方法,可以使子級的值成為父級的任意函數。 當必須将在變量中維護的每個線程屬性自動傳輸到建立的任何子線程時,可繼承的線程局部變量将優先于普通線程局部變量使用。
源碼:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
*在建立子線程時,根據父級值計算此可繼承線程局部變量的子級初始值。在啟動子級之前,從父線程中調用此方法。 此方法僅傳回其輸入參數,如果需要其他行為,則應重寫此方法。
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* 擷取與ThreadLocal關聯的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
建立與ThreadLocal關聯的Map
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
我們來操作一下:
public class SubInheritableThreadLocal extends InheritableThreadLocal<String> {
@Override
protected String initialValue() {
return "default value";
}
}
public class InheritableThreadLocalObj {
public static SubInheritableThreadLocal subInheritableThreadLocal = new SubInheritableThreadLocal();
}
public class InheritableThreadLocalT1 extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
System.out.println("current thread name:" + currentThread().getName() + " value:" + InheritableThreadLocalObj.subInheritableThreadLocal.get());
Thread.sleep(1000);
}
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
public class InheritableThreadLocalV1 {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println("current thread name :" + Thread.currentThread().getName() + " value:" + InheritableThreadLocalObj.subInheritableThreadLocal.get());
}
Thread.sleep(1000);
InheritableThreadLocalT1 inheritableThreadLocalT1 = new InheritableThreadLocalT1();
inheritableThreadLocalT1.setName("thread01");
inheritableThreadLocalT1.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運作結果:
那我們如何修改子線程中的值呢?我們隻需要重寫childValue方法就好了。
修改的代碼:
public class SubInheritableThreadLocal extends InheritableThreadLocal<String> {
@Override
protected String initialValue() {
return "default value";
}
@Override
protected String childValue(String parentValue) {
return "this is a sub thread name value " + Thread.currentThread().getName();
}
}
運作結果:
這個就是列印了子線程修改過的值。這裡要注意一點,如果子線程在進行get調用的時候,父線程在set的時候,子線程擷取的還是之前的舊值。