多線程 ThreadLocal

ThreadLocal使得各線程可以保持各自獨立的一個對象,並非經過ThreadLocal.set()來實現的,而是經過每一個線程中的new 對象 的操做來建立的對象,每一個線程建立一個,不是什麼對象的拷貝或副本。經過ThreadLocal.set()將這個新建立的對象的引用保存到各線程的本身的一個map中,每一個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從本身的map中取出放進去的對象,所以取出來的是各自本身線程中的對象,ThreadLocal實例是做爲map的key來使用的。java

 

下面從三個方面思考安全

ThreadLocal是什麼?有什麼用?怎麼用?多線程


ThreadLocal是什麼併發

早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。使用這個工具類能夠很簡潔地編寫出優美的多線程程序。ide

當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。函數

從線程的角度看,目標變量就象是線程的本地變量,這也是類名中「Local」所要表達的意思。工具

因此,在Java中編寫線程局部變量的代碼相對來講要笨拙一些,所以形成線程局部變量沒有在Java開發者中獲得很好的普及。this

ThreadLocal的接口方法spa

ThreadLocal類接口只有4個方法,咱們先來了解一下.net

void set(Object value);//設置當前線程的線程局部變量的值。

public Object get();//該方法返回當前線程所對應的線程局部變量。

public void remove();//將當前線程局部變量的值刪除,目的是爲了減小內存的佔用,該方法是JDK 5.0新增的方法。須要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。

protected Object initialValue();//返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,而且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

 

從JDK5.0開始,ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。

ThreadLocal是如何作到爲每個線程維護變量的副本的呢?

其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於存儲每個線程的變量副本,Map中元素的鍵爲線程對象,而值對應線程的變量副本。

 

ThreadLocal有什麼用

ThreadLocal 不是用於解決共享變量的問題的,不是爲了協調線程同步而存在,而是爲了方便每一個線程處理本身的狀態而引入的一個機制,理解這點對正確使用ThreadLocal相當重要
咱們先看一個簡單的例子:

import java.util.Locale;

public class ThreadLocalDemo {
private static ThreadLocal<Integer> local=new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;//初始值
}
};

public static void main(String[] args) {
Thread[] threads=new Thread[3];
for(int i=0;i<3;i++){
threads[i]=new Thread(new Runnable() {
@Override
public void run() {
int mun=local.get();
for(int j=0;j<10;j++){
mun=mun+1;
}
local.set(mun);
System.out.println(Thread.currentThread().getName()+"==="+local.get());

}
});
}
for(Thread t:threads){
t.start();
}

}
}

運行結果

Thread-3===10
Thread-2===10
Thread-1===10

咱們看到,每一個線程累加後的結果都是10,各個線程處理本身的本地變量值,線程之間互不影響。
下面咱們再來看一個例子

import java.util.Locale;

public class ThreadLocalDemo {
private static NumberList numlist=new NumberList();
private static ThreadLocal<NumberList> local=new ThreadLocal<NumberList>(){
@Override
protected NumberList initialValue() {
return numlist;//初始值
}
};

public static void main(String[] args) {
Thread[] threads=new Thread[3];
for(int i=0;i<3;i++){
threads[i]=new Thread(new Runnable() {
@Override
public void run() {
NumberList list=local.get();
for(int j=0;j<1000;j++){
list.init(); 
}
local.set(list);
System.out.println(Thread.currentThread().getName()+"==="+local.get().num);
//    
}
});
}
for(Thread t:threads){
t.start();
}

}

static class NumberList{
int num;
public void init(){
num++;
}
}
}

運行結果每次都不會同樣
第一次

Thread-3===2000
Thread-2===2000
Thread-1===3000

第二次

Thread-1===1372
Thread-3===2873
Thread-2===2774

上面代碼中,咱們經過覆蓋initialValue函數來給咱們的ThreadLocal提供初始值,每一個線程都會獲取這個初始值的一個副本。而如今咱們的初始值是一個定義好的一個對象,num是這個對象的引用。換句話說咱們的初始值是一個引用。引用的副本和引用指向的不就是同一個對象嗎?

若是咱們想給每個線程都保存一個NumberList 對象應該怎麼辦呢?那就是建立對象的副本而不是對象引用的副本:

private static ThreadLocal<NumberList> local=new ThreadLocal<NumberList>(){
@Override
protected NumberList initialValue() {
//    return numlist;//初始值已經定義好的的
return new NumberList();//每次都new一下
}
};

如今咱們應該能明白ThreadLocal本地變量的含義了吧。接下來咱們就來看看ThreadLocal的源碼,從內部來揭示它的神祕面紗。

ThreadLocal有一個內部類ThreadLocalMap,這個類的實現佔了整個ThreadLocal類源碼的一多半。這個ThreadLocalMap的做用很是關鍵,它就是線程真正保存線程本身本地變量的容器。每個線程都有本身的單獨的一個ThreadLocalMap實例,其全部的本地變量都會保存到這一個map中。如今就讓咱們從ThreadLocal的get和set

public T get() {
//獲取當前執行線程
Thread t = Thread.currentThread();
//取得當前線程的ThreadLocalMap實例
ThreadLocalMap map = getMap(t);
//若是map不爲空,說明該線程已經有了一個ThreadLocalMap實例
if (map != null) {
//map中保存線程的全部的線程本地變量,咱們要去查找當前線程本地變量
ThreadLocalMap.Entry e = map.getEntry(this);
//若是當前線程本地變量存在這個map中,則返回其對應的值
if (e != null)
return (T)e.value;
}
//若是map不存在或者map中不存在當前線程本地變量,返回初始值
return setInitialValue();
}

Thread對象都有一個ThreadLocalMap類型的屬性threadLocals,這個屬性是專門用於保存本身全部的線程本地變量的。這個屬性在線程對象初始化的時候爲null。因此對一個線程對象第一次使用線程本地變量的時候,須要對這個threadLocals屬性進行初始化操做。注意要區別 「線程第一次使用本地線程變量」和「第一次使用某一個線程本地線程變量」。

getMap方法:

//直接返回線程對象的threadLocals屬性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

setInitialValue方法:

private T setInitialValue() {
//獲取初始化值,initialValue 就是咱們以前覆蓋的方法
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//若是map不爲空,將初始化值放入到當前線程的ThreadLocalMap對象中
if (map != null)
map.set(this, value);
else
//當前線程第一次使用本地線程變量,須要對map進行初始化工做
createMap(t, value);
//返回初始化值
return value;
}

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);

if (map != null)
map.set(this, value);
//說明線程第一次使用線程本地變量(注意這裏的第一次含義)
else
createMap(t, value);
}

小結
ThreadLocal是解決線程安全問題一個很好的思路,它經過爲每一個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題。在不少狀況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的併發性。

原文連接:https://blog.csdn.net/baidu_23086307/article/details/56674454

相關文章
相關標籤/搜索