轉載請標明出處:
blog.csdn.net/forezp/arti…
本文出自方誌朋的部落格
什麼是執行緒封閉
當訪問共享變數時,往往需要加鎖來保證資料同步。一種避免使用同步的方式就是不共享資料。如果僅在單執行緒中訪問資料,就不需要同步了。這種技術稱為執行緒封閉。在Java語言中,提供了一些類庫和機制來維護執行緒的封閉性,例如區域性變數和ThreadLocal類,本文主要深入講解如何使用ThreadLocal類來保證執行緒封閉。
理解ThreadLocal類
ThreadLocal類能使執行緒中的某個值與儲存值的物件關聯起來,它提供了get、set方法,這些方法為每個使用該變數的執行緒儲存一份獨立的副本,因此get總是set當前執行緒的set最新值。
首先我們來看個例子,這個例子來自於www.cnblogs.com/dolphin0520…
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());
}
}複製程式碼
執行該程式,程式碼輸出的結果為:
1
main
10
Thread-0
1
main
從這段程式碼可以看出在mian執行緒和thread1執行緒確實都儲存著各自的副本,它們的副本各自不干擾。
ThreadLocal原始碼解析
來從原始碼的角度來解析ThreadLocal這個類,這個類存放在java.lang包,這個類有很多方法。
它內部又個ThreadLocalMap類,主要有set()、get()、setInitialValue 等方法。
首先來看下set方法,獲取當前Thread的 map,如果不存在則新建一個並設定值,如果存在設定值,原始碼如下:
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,可以發現它根據Thread建立來一個ThreadLocalMap。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}複製程式碼
t.threadLocals為當前執行緒的一個變數,也就是ThreadLocal的資料都是存放在當前執行緒的threadLocals變數裡面的,由此可見用ThreadLocal存放的資料是執行緒安全的。因為它對於不同的執行緒來,使用ThreadLocal的set方法都會根據執行緒判斷該執行緒是否存在它的threadLocals成員變數,如果沒有就建一個,有的話就存下資料。
ThreadLocal.ThreadLocalMap threadLocals = null;複製程式碼
ThreadLocalMap為ThreadLocal的一個內部類,原始碼如下:
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,要不然會報空指標異常。還有一種方式就是在初始化的時候呼叫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());
}
}複製程式碼
執行該程式,程式碼輸出的結果為:
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併發程式設計實戰》
《深入理解JVM》
關注我的公眾號
精彩內容不能錯過!