java 1.8 ThreadLocal原始碼分析
1. 關於ThreadLocal
當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。從執行緒的角度看,目標變數就象是執行緒的本地變數,這也是類名中“Local”所要表達的意思。
建立執行緒區域性私有的變數,避免多執行緒之間的競爭,是一種簡單的多執行緒安全類。
我對ThreadLocal的理解就是:某個資料是共享的,但是共享的粒度是執行緒內部,即該執行緒裡執行的所有程式碼,所有方法內獲取到的都是同一個值。
區別與其他的形式:如果不用ThreadLocal,而是使用static 變數,創建出的全域性共享的話,在多執行緒的環境下,需要同步(簡單的例子是多執行緒下的雙判定的單例模式),如資料庫連線管理類ConnectionManager,這個例子見下:
class ConnectionManager {
private static Connection connect = null;
public synchronized static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public synchronized static void closeConnection () {
if(connect!=null)
connect.close();
}
}
一個簡單的同步是直接使用synchronized 方法,但是這種的實現使得每次只能有一個執行緒在操作ConnectionManager.但是這種情況下,併發度太小(就這個情景下,使用連線池效能更好,這裡只是使用這個說明做個ThreadLocal引入的例子),變成每個執行緒一個connectionmanager,這樣的話,就不用在connectionmanager類中使用同步方法了,增加了處理時間。
那ThreadLocal和直接的將某個物件變成普通的區域性變數,有什麼不同呢?
如下面的程式碼:
class ConnectionManager {
private Connection connect = null;
public Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public void closeConnection() {
if(connect!=null)
connect.close();
}
}
我對次的理解是這樣的,加入有個管理類Manager,裡面有個成員是普通的成員變數SomeType obj,這樣,每個Manager例項都有一個obj物件,這和上面需求的每個Thread一份obj還是不同的。而要做到每個Thread一份obj,要麼將obj物件放到Thread子類或相關的類中(如Runnable等),或是,每建立一個Thread,手動的繫結一個obj物件,不然,是沒有辦法做到每個Thread一份obj。但是這樣的話,使得一些獨立的功能和Thread耦合起來了,或是使得你需要提前的需要知道Thread個數,好讓你手動的建立obj物件,獨立的繫結上去。一般來說,我們更希望一些工具類,管理類能夠獨立出去,而不是和Thread,Runanble雜在一起,能夠自動的感知有多少的Thread,就建立多少的拷貝,這就是使用ThreadLocal的好處!!
2. 使用例子
class Manager{
static ThreadLocal< String> s=new ThreadLocal<String>();;
}
public class Test implements Runnable{
public static void main(String args[])
{
Test r1=new Test();
Thread s1=new Thread(r1);
Thread s2=new Thread(r1);
s1.start();
s2.start();
}
@Override
public void run() {
if(Manager.s.get()==null)
Manager.s.set(new String(Thread.currentThread().getName()));
System.out.println(Manager.s.get());
//use it do something else
}
}
3. 方法分析
a.建構函式
public ThreadLocal() { }
b.get()
public T get()
{
Thread t = Thread.currentThread();
//獲取與當前執行緒繫結的ThreadLocalMap物件
//getMap(t) -> return t.threadLocals;
ThreadLocalMap map = getMap(t);
if (map != null)
{
//在hashmap中查詢以當前ThreadLocal為key物件的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//因為ThreadLocaMap是懶載入的,即存在一個有效的條目時,才會創建出
//與當前Thread相關的threadLocalMap物件,所以,在set呼叫前,呼叫了
//get,會得到null的threadLocalMap物件
return setInitialValue();
}
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Thread物件中成員變數:ThreadLocal.ThreadLocalMap threadLocals = null;
關於ThreadLocal.ThreadLocalMap類:簡而言之,就是一個map,裡面的每個條目Entry的key為WeakReference型別的物件,value為該變數的該執行緒中的實際值(即如果如果該執行緒中的ThreadLocal物件沒有其他物件引用了的話,該Entry就會被回收)
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?> > {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//ThreadLocalMap是一個線性探測hash表,預設的初始容量為16,threshold=2/3,ThreadLocal為key,當新增entry時,使用ThreadLocal的hashcode來線上性表中查詢合適的位置
private Entry getEntry(ThreadLocal<?> key)
{
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
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);
}
//threadlocalmap的set方法
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)])
{
ThreadLocal<?> k = e.get();
if (k == key)
{
e.value = value;
return;
}
if (k == null)
{
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
4. 總結
- ThreadLocal有一個final型別的int變數(int threadLocalHashCode = nextHashCode()),表示該TreadLocal變數的hash值,該hash值有一個全域性的AtomicInteger來管理,即系統中ThreadLocal變數有同一個AtomicInteger變數自增而得,因為Thread數母隨機,基本能保證同一個Thread中的ThreadLocal變數hashcode不斷變化,使得能均勻的對映到ThreadLocalMap上。
- 每個執行緒Thread物件都有一個ThreadLocal.ThreadLocalMap物件,執行緒的ThreadLocal變數存放在該ThreadLocalMap中。
- ThreadLocal為懶載入的,即當使用到第一個ThreadLocal物件時,才會創建出執行緒的ThreadLocalMap物件,該map為執行緒探測線性map,初始的預設大小為16,threshold為2/3.
- ThreadLocalMap使用迴圈陣列存放兼值對ThreadLocalMap.Entry為WeakSoftReference子類,即當系統中不存在對ThreadLocal物件時,該Entry就會被回收(由於沒有建立該WeakSoftReference物件相關的 reference queue,所以,只保證在記憶體不充足的時候該Entry被回收)
- 在ThreadLocalMap中查詢時,首先使用ThreadLocal物件(key)的hashcode定位可能資料索引,然後依次遍歷,直到找到對應的Entry,或是遇到null的Entry