參考資料:http://www.cnblogs.com/dolphin0520/p/3920407.html
先來看看爲什麼用ThreadLocal,上一篇博客說的很好了,就好比,你要讓你的線程鏈接數據庫,如果你讓這些線程共享一個數據庫鏈接的話,就會出問題:
代碼來自參考博客:
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
當一個線程正在使用的時候,其他的線程可能會關閉這個鏈接,肯定就出問題了,要解決這個問題,你除了在每個線程單獨的鏈接之外還可以使用ThreadLocal來解決這個問題。
我們把問題簡化:我讓三個線程隨機輸出5到1
先看代碼:
class MyThread extends Thread {
private int i = 5;
@Override
public void run() {
for(int j = 0; j<20; j++) {
synchronized (this) {
if(i>0) {
System.out.println(
Thread.currentThread().getName() + ";" + i--);
}
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
運行結果:
現在,我想讓每個 線程都重5到1輸出一遍,這個問題可以有其他的結局問題,這裏討論的是ThreadLocal,就用ThreadLocal解決
代碼:
class MyThread extends Thread {
private ThreadLocal<Integer> i = new ThreadLocal<Integer>();
public void set() {
i.set(5);
}
public void run() {
set();
int si = i.get();
for(int j = 0; j<20; j++) {
si = i.get();
if(si>0) {
System.out.println(Thread.currentThread().getName() + ";" + si-- );
i.set(si);
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
結果:
這樣就相當於每個線程內部都有一個 i 這個i 只有當前線程自己能訪問,很多地方把ThreadLocal叫做線程本地變量的原因。
現在來看看ThreadLocal怎麼保存變量副本的:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
這是ThreadLocal中經常用到的幾個方法。
先看看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);
//通過getMap(t)方法得到一個ThreadLocalMap
if (map != null) {
//如果map不爲空,這將ThreadLocal作爲鍵值,從map中取出一個Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//如果這個Entry不爲空,就將他的值返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果當前map爲null,或者entry爲空,調用setInitialValue()方法
return setInitialValue();
}
/*
* 再來看看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);
/*取出當前線程的map,如果爲null則創建一個*/
if (map != null)
map.set(this, value);
else
createMap(t, value);
/*這個方法最終會返回一個值,不管是創建一個map還是往裏面放一個值*/
return value;
}
/*最後來看看ThreadLocalMap ,這個map到底是什麼*/
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
/*threadLocals定義在,Thread.java中,這是Thread.java中的源碼*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* 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是ThreadLocal中的一個內部類*/
現在理解ThreadLocal也就簡單一點了:
首先,每個Thread中有一個ThreadLocal的內部類ThreadLocalMap的成員變量threadLocals ,這個threadLocals 就是來保存當前Thread的實際的變量副本的,鍵值爲當前ThreadLocal變量,value爲變量副本(即T類型的變量)。
再來說get(),get()會返回當前ThreadLocal爲鍵的threadLocals中的值,
如果set()之前 get()這時候 會報空指針異常,(原因稍後再說)
然後看看set(T value)的代碼:
/**
* 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);
}
這時候看就容易了,
然後就是總結了:
1)實際的通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中的;
2)每個線程中可有多個threadLocal變量,記錄不同的副本,類似id,name
3)在進行get之前,必須先set,否則會報空指針異常;
然後就是怎麼處理這個空指針異常:
private T setInitialValue() {
/*這個可以理解爲得到一個值*/
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
/*取出當前線程的map,如果爲null則創建一個*/
if (map != null)
map.set(this, value);
else
createMap(t, value);
/*這個方法最終會返回一個值,不管是創建一個map還是往裏面放一個值*/
return value;
}
如果你沒有set()就調用get(),T value = initialValue();這個方法返回的應該是null,這就是原因。
所有如果你不想空指針異常就要重寫這個方法:
例如:
class MyThread extends Thread {
private ThreadLocal<Integer> i = new ThreadLocal<Integer>();
public void set() {
i.set(5);
}
public void run() {
/*如果將這個 set()去掉,程序會空指針異常*/
// set();
int si = i.get();
for(int j = 0; j<20; j++) {
si = i.get();
if(si>0) {
System.out.println(Thread.currentThread().getName() + ";" + si-- );
i.set(si);
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
這時如果自己重寫initialValue()方法就可以避免:
class MyThread extends Thread {
private ThreadLocal<Integer> i = new ThreadLocal<Integer>(){
protected Integer initialValue() {
return 5;
};
};
public void set() {
i.set(5);
}
public void run() {
/*這時就去掉set()不會報錯*/
//set()
int si = i.get();
for(int j = 0; j<20; j++) {
si = i.get();
if(si>0) {
System.out.println(Thread.currentThread().getName() + ";" + si-- );
i.set(si);
}
}
}
}
public class TestMain {
public static void main(String ar[]) {
MyThread m1 = new MyThread();
Thread mt1 = new Thread(m1);
Thread mt2 = new Thread(m1);
Thread mt3 = new Thread(m1);
mt1.start();
mt2.start();
mt3.start();
}
}
結果:
今晚就寫到這裏了,如果有錯誤,歡迎討論!以後要是深入研究會更新的