轉(zhuǎn)載請標(biāo)明出處:
http://blog.csdn.net/forezp/article/details/77620769
本文出自方志朋的博客
什么是線程封閉
當(dāng)訪問共享變量時户辱,往往需要加鎖來保證數(shù)據(jù)同步。一種避免使用同步的方式就是不共享數(shù)據(jù)甘苍。如果僅在單線程中訪問數(shù)據(jù),就不需要同步了。這種技術(shù)稱為線程封閉。在Java語言中融柬,提供了一些類庫和機制來維護線程的封閉性颊咬,例如局部變量和ThreadLocal類务甥,本文主要深入講解如何使用ThreadLocal類來保證線程封閉。
理解ThreadLocal類
ThreadLocal類能使線程中的某個值與保存值的對象關(guān)聯(lián)起來喳篇,它提供了get敞临、set方法,這些方法為每個使用該變量的線程保存一份獨立的副本麸澜,因此get總是set當(dāng)前線程的set最新值挺尿。
首先我們來看個例子,這個例子來自于http://www.cnblogs.com/dolphin0520/p/3920407.html
public class Test1 {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test1 test = new Test1();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread(() -> {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
});
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
運行該程序,代碼輸出的結(jié)果為:
1
main
10
Thread-0
1
main
從這段代碼可以看出在mian線程和thread1線程確實都保存著各自的副本编矾,它們的副本各自不干擾熟史。
ThreadLocal源碼解析
來從源碼的角度來解析ThreadLocal這個類,這個類存放在java.lang包窄俏,這個類有很多方法以故。
它內(nèi)部又個ThreadLocalMap類,主要有set()裆操、get()怒详、setInitialValue 等方法。
首先來看下set方法踪区,獲取當(dāng)前Thread的 map昆烁,如果不存在則新建一個并設(shè)置值,如果存在設(shè)置值缎岗,源碼如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
跟蹤createMap静尼,可以發(fā)現(xiàn)它根據(jù)Thread創(chuàng)建來一個ThreadLocalMap。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
t.threadLocals為當(dāng)前線程的一個變量传泊,也就是ThreadLocal的數(shù)據(jù)都是存放在當(dāng)前線程的threadLocals變量里面的鼠渺,由此可見用ThreadLocal存放的數(shù)據(jù)是線程安全的。因為它對于不同的線程來眷细,使用ThreadLocal的set方法都會根據(jù)線程判斷該線程是否存在它的threadLocals成員變量拦盹,如果沒有就建一個,有的話就存下數(shù)據(jù)溪椎。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap為ThreadLocal的一個內(nèi)部類普舆,源碼如下:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看到ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值校读。
在使用ThreadLocal的get方法之前一定要先set沼侣,要不然會報空指針異常。還有一種方式就是在初始化的時候調(diào)用initialValue()方法賦值歉秫。改造下之前的例子蛾洛,代碼如下:
public class Test2 {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){
@Override
protected Long initialValue() {
return Thread.currentThread().getId();
}
};
ThreadLocal<String> stringLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return Thread.currentThread().getName();
}
};
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test2 test = new Test2();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread(() -> {
System.out.println(test.getLong());
System.out.println(test.getString());
});
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
運行該程序,代碼輸出的結(jié)果為:
1
main
10
Thread-0
1
main
ThreadLocal常用的使用場景
通常講JDBC連接保存在ThreadLocal對象中雁芙,每個對象都有屬于自己的連接轧膘,代碼如下:
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
參考資料
《Java并發(fā)編程實戰(zhàn)》
《深入理解JVM》