ThreadLocal簡單學習
學習Struts2時,遇到ThreadLocal,不甚理解,所以對此作了一番學習。
ThreadLocal是什麼呢?首先這並不是一個線程類,它是用來提供本地線程數據,也就是它保存的數據是線程相關的,每個線程都有一份數據副本,一個線程可以對其保存的副本進行修改,卻不會影響其它的線程中的數據。這樣說不易理解,比較這三種情況的數據,1、非ThreadLocal類型的靜態成員數據;2、非ThreadLocal類型的非靜態成員數據;3、ThreadLocal類型的靜態數據。第一種情況中的數據是類類型的,所以此變量是所有線程共享的,第二種情況中的數據是實例對象相關的數據,是線程相關的,不同線程中實例數據的修改是相互不影響的。第三種雖然是靜態類型的數據,但是由於是通過ThreadLocal保存的,所以還是線程相關的。由第三種類型的數據和第一種類型的數據之間的對比,可以幫助瞭解ThreadLocal的功能。
ThreadLocal的接口功能比較簡單,常用的爲get、set、remove,它們的函數簽名如下
get接口public T get();
set接口public void set(T);
remove接口public void remove();
下面用一個簡單的項目來說明ThreadLocal的功能。項目內容很簡單,主要包括三個類TestThread、Main和TestClass
TestClass.java代碼如下
import java.util.Random;
public class TestClass
{
private static int value=0;
public int nonStaticValue ;
private static ThreadLocal<Integer> threadLocalValue= new ThreadLocal<Integer>();
public TestClass(){
Random rand= new Random();
nonStaticValue =rand.nextInt(100);
value ++;
}
public static Integer
getLocalThreadValue(){
return threadLocalValue .get();
}
public static void setLocalThreadValue(Integer
val){
threadLocalValue .set(val);
}
public static int getStaticValue(){
return value ;
}
public int getNonStaticValue(){
return nonStaticValue ;
}
}
TestThread.java代碼如下
public class TestThread extends Thread
{
private int id ;
TestClass clz;
public TestThread( int id)
{
super ("thread-" +id);
this .id =
id;
System. out .println("thread
in test thread constructor "
+ Thread. currentThread().getName());
}
@Override
public void run()
{
TestClass. setLocalThreadValue( id);
clz = new TestClass();
int maxRuntime=2000;
int step=1000;
int totalRuntime=0;
while (totalRuntime<maxRuntime)
{
totalRuntime+=step;
System. out .println(Thread.currentThread().getName()
+ "[non-static-value:"
+ clz.getNonStaticValue()
+ ",static-value:"
+ TestClass. getStaticValue() + ",thread
local value:"
+ TestClass. getLocalThreadValue() + "]");
try {
Thread. sleep(2000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
}
}
}
Main.java代碼如下
public class Main extends Thread{
public int num ;
public Main(String
name, int num){
super (name);
this .num =num;
}
@Override
public void run(){
for (int i=0;i<3;i++){
try {
Thread. sleep(1000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
num++;
System. out .println(Thread.currentThread().getName()+ "[
num = "+ num+ "
]");
}
}
public static void main(String[]
args) {
int num=3;
TestThread[] threads= new TestThread[num];
for (int i=0;i<num;i++){
threads[i]= new TestThread(i);
}
for (int i=0;i<num;i++){
threads[i].start();
}
System. out .println("--------------------------------------+\t\r" );
num=2;
Main[] mains= new Main[num];
for (int i=0;i<num;i++){
mains[i]= new Main("main
thread" +i,i);
}
for (int i=0;i<num;i++)
mains[i].start();
}
}
運行之後的輸出結果如下
thread in test thread constructor main
thread in test thread constructor main
thread in test thread constructor main
--------------------------------------
thread-0[non-static-value:26,static-value:3,thread local value:0]
thread-1[non-static-value:49,static-value:3,thread local value:1]
thread-2[non-static-value:59,static-value:3,thread local value:2]
main thread0[ num = 1 ]
main thread1[ num = 2 ]
thread-0[non-static-value:26,static-value:3,thread local value:0]
thread-1[non-static-value:49,static-value:3,thread local value:1]
thread-2[non-static-value:59,static-value:3,thread local value:2]
main thread0[ num = 2 ]
main thread1[ num = 3 ]
main thread0[ num = 3 ]
main thread1[ num = 4 ]
看其中的輸出,非靜態的成員變量(私有或公有)是線程相關的,靜態的類變量是多個線程共享的,而由靜態的ThreadLocal對象保存的數據,依舊是線程相關的,各個線程之間的值是有區別的。
[ThreadLocal的實現原理]
要理解ThreadLocal的實現原理,需要了解ThreadLocalMap類。TThreadLocalMap有一個Entry類型的數組,每個Entry是一個<ThreadLocal,Object>的鍵值對,在數組中的索引由ThreadLocal對象的哈希值和當前數組的長度值減一做與操作而來,如果出現衝突,就往後查找。ThreadLocalMap類中插入鍵值對數據的set方法如下所示
private void set(ThreadLocal
key, Object value) {
Entry[] tab = table;
int len
= tab.length;
int i
= key.threadLocalHashCode & (len-1); //首先獲取數組索引值
/*
從計算出來的索引點開始比較,如果能找到相應的key,就更新值
*/
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); //沒有找到相應的key,插入新數據
int sz
= ++size;
if (!cleanSomeSlots(i,
sz) && sz >= threshold)
rehash();
}
每個Thread類型的線程都有個ThreadLocalMap類型的實例變量threadlocals,用來存放與此線程相關的ThreadLocal變量。再來看ThreadLocal保存數據的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);
}
首先獲取當前的線程t,由getMap方法獲取當前線程的threadLocals屬性map,然後保存鍵值對。在保存的時候採用this對象做爲鍵,同樣在獲取數據的時候,也是以this作爲鍵來獲取相應的數據。因爲一個線程可能有多個由ThreadLocal對象保存的變量,採用this作爲key,可以實現這樣不同ThreadLocal對象之間的區分。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.