ThreadLocal的初步認識
在正式解析ThreadLocal之前,我們先來看 一個例子:
public class ThreadLocalTest {
//創建并初始化一個ThreadLocal變量
private static ThreadLocal<Integer> cnt = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args){
System.out.println(Thread.currentThread().getName() + " before write, get cnt " + cnt.get());
cnt.set(1);
//創建線程一對ThreadLocal變量進行修改和讀取
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " before write, get cnt : " + cnt.get());
cnt.set(2);
System.out.println(Thread.currentThread().getName() + " after write, get cnt : " + cnt.get());
}
}.start();
//創建線程二對ThreadLocal變量進行修改和讀取
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " before write, get cnt : " + cnt.get());
cnt.set(3);
System.out.println(Thread.currentThread().getName() + " get cnt : " + cnt.get());
}
}.start();
//主線程進行讀取
System.out.println(Thread.currentThread().getName() + " after write, get cnt : " + cnt.get());
}
}
輸出結果:
main before write, get cnt 0
Thread-0 before write, get cnt : 0
Thread-0 after write, get cnt : 2
main after write, get cnt : 1
Thread-1 before write, get cnt : 0
Thread-1 get cnt : 3
程序中存在三個線程(包括主線程)對變量cnt進行修改并讀取,根據結果,我們發現不同線程之間對變量的修改和讀取都是獨立的,互不影響的。實際上,這就是ThreadLocal的特色。
ThreadLocal在每個線程中對變量會創建一個副本,即每個線程內部都會有一個變量,且在線程內部任何地方可以使用,線程之間互不影響。
ThreadLocal的深入理解
ThreadLocal類主要提供以下幾個方法:
public T get() {}
public void set(T value) {}
public void remove() {}
protected T initValue() {}
- 我們先看到initValue方法:
protected T initialValue() {
return null;
}
我們會發現這是個空方法,因此,我們在進行初始化時,必須重寫該方法才能執行初始化操作。
- 接下來,我們繼續解析get方法:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
首先通過Thread.currentThread()
方法獲取到當前線程,然后再根據當前線程調用getMap
方法獲取到ThreadLocalMap對象。那么我們來看看getMap
方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
我們可以看到它返回的是線程中的一個threadLocals
變量,而我們在Thread類中,也可以發現這個變量:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
若map不為空,那么將通過map.getEntry(this)
方法來獲取到Entry對象,我們來看看Entry對象到底是什么:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
也就是說,Entry對象是ThreadLocal的一個弱引用而已,這里應該是方便GC的回收,防止內存溢出。我們繼續看到getEntry方法:
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);
}
看到這里,可能就有些懵逼了,這里的threadLocalHashCode
和table
完全不知所云。這里,我們就要回頭看ThreadLocal中的一些變量:
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
*The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
在這里定義了一個類型為AtomicInteger
類型的變量,即每次創建一個ThreadLocal對象,都會得到它所對應的值,因為它是自增的,然后threadLocalHashCode
變量則為該值得一個hash值。而table變量則是聲明在ThreadLocalMap中:
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
它的作用為存儲值,其中threadLocalHashCode
經過處理后便為ThreadLocal所對應的索引。
獲取到Entry對象后,我們就可以通過Entry對象獲取到所對應的值,返回即可。
若map為空,則調用setInitialValue
方法。
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}
方法中,獲取到起初始值,并將當前線程與value進行綁定,存入map對象中,以便下一次的調用,若map為空,則創建map對象。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我們可以看到就是返回一個ThreadLocalMap對象。
- 接下來我們看到set方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
這里的思路很簡單,就是先獲取到map對象,若map不為空,則更新其值,否則創建一個map對象。
- 最后我們看到remove方法:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
即獲取到map對象后,將map中的值移除。
注意:在使用時,若沒有重寫initialValue
方法,那么在調用get方法前,必須調用set方法,否則會報空指針異常。
TheadLocal的應用
通常當我們想要共享一個變量,但該變量又不是線程安全的,并且也不想通過加鎖機制來降低并發性,那么就可以用ThreadLocal來維護一個線程一個實例。
- 應用一:數據庫連接
public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder =
new ThreadLocal<Connection>(){
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection("localhost","username","password");
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
};
public static Connection getConnection(){
return connectionHolder.get();
}
}