多線程中的ThreadLocal
大家通常知道庐橙,ThreadLocal類可以幫助我們實(shí)現(xiàn)線程的安全性独撇,這個(gè)類能使線程中的某個(gè)值與保存值的對(duì)象關(guān)聯(lián)起來糠悼。ThreadLocal提供了get與set等訪問接口或方法形真,這些方法為每個(gè)使用該變量的線程都存有一份獨(dú)立的副本抡砂,因此get總是返回由當(dāng)前執(zhí)行線程在調(diào)用set時(shí)設(shè)置的最新值幼苛。從概念上看窒篱,我們把ThreadLocal<T>理解成一個(gè)包含了Map<Thread,T>的對(duì)象,其中Map的key用來標(biāo)識(shí)不同的線程舶沿,而Map的value存放了特定該線程的某個(gè)值墙杯。但是ThreadLocal的實(shí)現(xiàn)并非如此,我們以這樣的理解方式去使用ThreadLocal也并不能實(shí)現(xiàn)真正的線程安全括荡。
這些特定于線程的值是保存在當(dāng)前的Thread對(duì)象中高镐,并非保存在ThreadLocal對(duì)象中。并且我們發(fā)現(xiàn)Thread對(duì)象中保存的是Object對(duì)象的一個(gè)引用畸冲,這樣的話嫉髓,當(dāng)有其他線程對(duì)這個(gè)引用指向的對(duì)象做修改時(shí),當(dāng)前線程Thread對(duì)象中保存的值也會(huì)發(fā)生變化邑闲。
public class NotSafeThread implements Runnable {
public static Number number = new Number();
public static int i = 0;
public void run() {
//每個(gè)線程計(jì)數(shù)加一
number.setNum(i++);
//將其存儲(chǔ)到ThreadLocal中
value.set(number);
//延時(shí)2秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
}
//輸出num值
System.out.println(value.get().getNum());
}
public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
};
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
newCachedThreadPool.execute(new NotSafeThread());
}
}
}
運(yùn)行程序算行,輸出:
4
4
4
4
4
為什么每個(gè)線程都輸出4?難道他們沒有獨(dú)自保存自己的Number副本嗎苫耸?為什么其他線程還是能夠修改這個(gè)值州邢?我們看一下ThreadLocal的源碼:
public void set(Object obj)
{
Thread thread = Thread.currentThread();//獲取當(dāng)前線程
ThreadLocalMap threadlocalmap = getMap(thread);
if(threadlocalmap != null)
threadlocalmap.set(this, obj);
else
createMap(thread, obj);
}
其中g(shù)etMap方法:
ThreadLocal.ThreadLocalMap getMap(Thread thread)
{
return thread.inheritableThreadLocals; //返回的是thread的成員變量
}
這也就是為什么上面的程序?yàn)槭裁磿?huì)輸出一樣的結(jié)果:5個(gè)線程中保存的是同一Number對(duì)象的引用,在線程睡眠2s的時(shí)候褪子,其他線程將num變量進(jìn)行了修改量淌,因此它們最終輸出的結(jié)果是相同的。
那么褐筛,ThreadLocal的 “為每個(gè)使用該變量的線程都存有一份獨(dú)立的副本,因此get總是返回由當(dāng)前執(zhí)行線程在調(diào)用set時(shí)設(shè)置的最新值叙身∮嬖”這句話中的“獨(dú)立的副本”,也就是我們理解的“線程本地存儲(chǔ)”只能是每個(gè)線程所獨(dú)有的對(duì)象并且不與其他線程進(jìn)行共享信轿,大概是這樣的情況:
public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
public Number initialValue(){//為每個(gè)線程保存的值進(jìn)行初始化操作
return new Number();
}
};
或者
public void run() {
value.set(new Number());
}