ThreadLocal谭溉,顧名思義捕仔,這是本地線程的意思匕积,不好意思,這次顧錯(cuò)了榜跌。
ThreadLocal類是用來提供線程內(nèi)部的局部變量闪唆。這種變量在多線程環(huán)境下訪問(通過get或set方法訪問)時(shí)能保證各個(gè)線程里的變量相對獨(dú)立于其他線程內(nèi)的變量。ThreadLocal實(shí)例通常來說都是private static類型的钓葫,用于關(guān)聯(lián)線程和線程的上下文悄蕾。
按照我的理解,ThreadLocal就像是一個(gè)存儲線程公共值的map础浮,key是當(dāng)前線程帆调,value是這個(gè)公共值,各個(gè)線程之間對該公共值的使用互不影響豆同,相互獨(dú)立番刊。
一、案例引入
舉一個(gè)例子來說明一下影锈,如每個(gè)人剛出生都是0歲芹务,假設(shè)每個(gè)人能活100年,那么他去世的年齡就是100歲鸭廷。
1枣抱、(不使用ThreadLocal情況下)
先定義一個(gè)Person類:
package com.bxw.concurrent.threadLocal;
public class Person {
private static int age = 0;
public void grow(){
age++;
}
public int getAge(){
return age;
}
}
再定義一個(gè)PersonThread類:
package com.bxw.concurrent.threadLocal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PersonThread extends Thread{
private Person person;
PersonThread(Person person){
this.person = person;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"的出生年齡是" + person.getAge());
for(int i = 0; i < 100; i++){
person.grow();
}
System.out.println(Thread.currentThread().getName()+"的死亡年齡是" + person.getAge());
}
public static void main(String[] args) {
Person person = new Person();
ExecutorService executorService = Executors.newFixedThreadPool(4);
for(int i = 0; i < 4; i++){
executorService.submit(new PersonThread(person));
}
executorService.shutdown();
}
}
運(yùn)行結(jié)果:
可以看到四個(gè)線程的死亡年齡分別是100,200,300,400。這就說明了這四個(gè)線程共享了年齡這個(gè)靜態(tài)變量靴姿,線程安全出了問題沃但。
那么如何讓每個(gè)線程都獨(dú)享一個(gè)靜態(tài)變量呢,看看下面對Person的改造
2佛吓、(使用ThreadLocal情況下)
package com.bxw.concurrent.threadLocal;
public class Person {
private static int age = 0;
private static MyThreadLocal<Integer> ageLocal = new MyThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public void grow(){
ageLocal.set(ageLocal.get() + 1);
}
public int getAge(){
return ageLocal.get();
}
}
PersonThread不用改變宵晚,再次運(yùn)行,運(yùn)行結(jié)果:
這時(shí)维雇,每個(gè)線程的出生年齡都是0淤刃,死亡年齡都是100,說明每個(gè)線程都擁有自己的靜態(tài)變量吱型,相互獨(dú)立逸贾,不相互影響。
調(diào)整下PersonThread中線程池線程個(gè)數(shù)
ExecutorService executorService = Executors.newFixedThreadPool(4);
再次運(yùn)行,可以看到線程三铝侵,活了兩個(gè)100年灼伤,那么線程三死亡的時(shí)候年齡是200,再次說明這個(gè)靜態(tài)變量被單個(gè)線程獨(dú)立使用咪鲜。
三狐赡、ThreadLocal方法介紹
public void set(T value):將值放入線程局部變量中
public T get():從線程局部變量中獲取值
public void remove():從線程局部變量中移除值
protected T initialValue():線程局部變量初始值(initialValue時(shí)protected的,就是要提醒這個(gè)方法要重寫)
四疟丙、實(shí)現(xiàn)窮人版ThreadLocal
了解了ThreadLocal的基本原理颖侄,可以仿照ThreadLocal寫個(gè)簡易版的ThreadLocal
package com.bxw.concurrent.threadLocal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal<T> {
private Map<Thread, T> container = Collections.synchronizedMap(new HashMap<Thread, T>());
public void set(T value) {
container.put(Thread.currentThread(), value);
}
public T get() {
Thread thread = Thread.currentThread();
T value = container.get(thread);
if (value == null && !container.containsKey(thread)) {
value = initialValue();
container.put(thread, value);
}
return value;
}
public void remove() {
container.remove(Thread.currentThread());
}
protected T initialValue() {
return null;
}
}
將PersonThread中的ThreadLocal替換成MyThreadLocal,
private static MyThreadLocal<Integer> ageLocal = new MyThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
運(yùn)行結(jié)果與ThreadLocal一模一樣享郊。览祖。。
五炊琉、JDK中使用ThreadLocal的類(比如BigDecimal)
BigDecimal中有一個(gè)靜態(tài)內(nèi)部類StringBuilderHelper展蒂,BigDecimal中使用到了ThreadLocal設(shè)置了初始值(一個(gè)單獨(dú)的StringBuilderHelper對象),就可以實(shí)現(xiàn)讓BigDecimal在多線程環(huán)境下苔咪,每個(gè)線程都擁有一個(gè)StringBuilderHelper對象玄货。
六、總結(jié)
當(dāng)一個(gè)類中的變量(無論是否靜態(tài))在并發(fā)條件下需要考慮線程安全悼泌,就需要使用ThreadLocal松捉,讓ThreadLocal保證每個(gè)線程都獨(dú)立擁有自己的變量。