前面的文章裏,咱們學習了有關鎖的使用,鎖的機制是保證同一時刻只能有一個線程訪問臨界區的資源,也就是經過控制資源的手段來保證線程安全,這當然是一種有效的手段,但程序的運行效率也所以大大下降。那麼,有沒有更好的方式呢?答案是有的,既然鎖是嚴格控制資源的方式來保證線程安全,那咱們能夠反其道而行之,增長更多資源,保證每一個線程都能獲得所需對象,各自爲營,互不影響,從而達到線程安全的目的,而ThreadLocal即是採用這樣的思路。安全
ThreadLocal翻譯成中文的話大概能夠說是:線程局部變量,也就是隻有當前線程可以訪問。它的設計做用是爲每個使用該變量的線程都提供一個變量值的副本,每一個線程都是改變本身的副本而且不會和其餘線程的副本衝突,這樣一來,從線程的角度來看,就好像每一個線程都擁有了該變量。bash
下面是一個簡單的實例:ide
public class ThreadLocalDemo {
static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0;i<3;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int value = local.get();
System.out.println(Thread.currentThread().getName() + ":" + value);
local.set(value + 1);
}
}
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
}
}
複製代碼
上面的代碼不難理解,首先是定義了一個名爲 local
的ThreadLocal變量,並初識變量的值爲0,而後是定義了一個實現Runnable接口的內部類,在其run方法中對local
的值作讀取和加1的操做,最後是main方法中開啓兩個線程來運行內部類實例。函數
以上就是代碼的大概邏輯,運行main函數後,程序的輸出結果以下:學習
Thread-0:0
Thread-1:0
Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2
複製代碼
從結果能夠看出,雖然兩個線程都共用一個Runnable實例,但兩個線程中所展現的ThreadLocal的數據值並不會相互影響,也就是說這種狀況下的local
變量保存的數據至關因而線程安全的,只能被當前線程訪問。ui
那麼ThreadLocal內部是怎麼保證對象是線程私有的呢?毫無疑問,答案須要從源碼中查找。回顧前面的代碼,能夠發現其中調用了ThreadLocal的兩個方法set 和 get,咱們就從這兩個方法入手。this
先看 set() 的源碼:spa
public void set(T value) {
Thread t = Thread.currentThread();
// 獲取線程的ThreadLocalMap,返回map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//map爲空,建立
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
複製代碼
set的代碼邏輯比較簡單,主要是把值設置到當前線程的一個ThreadLocalMap對象中,而ThreadLocalMap能夠理解成一個Map,它是定義在Thread類中內部的成員,初始化是爲null,線程
ThreadLocal.ThreadLocalMap threadLocals = null;
複製代碼
不過,與常見的Map實現類,如HashMap之類的不一樣的是,ThreadLocalMap中的Entry是繼承於WeakReference類的,保持了對 「鍵」 的弱引用和對 「值」 的強引用,這是類的源碼:翻譯
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;
}
}
//省略剩下的源碼
....................
}
複製代碼
從源碼中中能夠看出,Entry構造函數中的參數 k 就是ThreadLocal實例,調用super(k) 代表對 k 是弱引用,使用弱引用的緣由在於,當沒有強引用指向 ThreadLocal 實例時,它可被回收,從而避免內存泄露,那麼爲什麼須要防止內存泄露呢?緣由下面會說到。
接着說set方法的邏輯,當調用set方法時,實際上是將數據寫入threadLocals這個Map對象中,這個Map的key爲ThreadLocal當前對象,value就是咱們存入的值。而threadLocals自己能保存多個ThreadLocal對象,至關於一個ThreadLocal集合。
接着看 get() 的源碼:
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;
}
}
//設置初識值到ThreadLocal中並返回
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;
}
複製代碼
get方法的邏輯也是比較簡單的,就是直接獲取當前線程的ThreadLocalMap對象,若是該對象不爲空就返回它的value值,不然就把初始值設置到ThreadLocal中並返回。
看到這,咱們大概就能明白爲何ThreadLocal能實現線程私有的原理了,其實就是每一個線程都維護着一個ThreadLocal的容器,這個容器就是ThreadLocalMap,能夠保存多個ThreadLocal對象。而調用ThreadLocal的set或get方法其實就是對當前線程的ThreadLocal變量操做,與其餘線程是分開的,因此才能保證線程私有,也就不存在線程安全的問題了。
然而,該方案雖然能保證線程私有,但卻會佔用大量的內存,由於每一個線程都維護着一個Map,當訪問某個ThreadLocal變量後,線程會在本身的Map內維護該ThreadLocal變量與具體實現的映射,若是這些映射一直存在,就代表ThreadLocal 存在引用的狀況,那麼系統GC就沒法回收這些變量,可能會形成內存泄露。
針對這種狀況,上面所說的ThreadLocalMap中Entry的弱引用就起做用了。
最後,總結一下ThreadLocal和同步機制之間的區別吧。
實現機制:
同步機制採用了「以時間換空間」的方式,控制資源保證同一時刻只能有一個線程訪問。
ThreadLocal採用了「以空間換時間」的方式,爲每個線程都提供一份變量的副本,從而實現同時訪問而互不影響,但由於每一個線程都維護着一份副本,對內存空間的佔用會增長。
數據共享:
同步機制是對公共資源作控制訪問的方式來保證線程安全,但資源還是共享狀態,可用於線程間的通訊;
ThreadLocal是每一個線程都有本身的資源(變量)副本,互相之間不影響,也就不存在共享的說法了。