轉(zhuǎn)載請(qǐng)注明文章出處LooperJing杯瞻!
在上篇文章Android源碼解析Handler系列第(一)篇說了Message的內(nèi)部維持的全局池機(jī)制。這一篇仍然是準(zhǔn)備知識(shí)允坚,因?yàn)樵贖andler中有ThreadLocal的身影茫多,大家知道,Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng)魏保,那么Handler內(nèi)部如何獲取到當(dāng)前線程的Looper呢?這就要使用ThreadLocal了摸屠,ThreadLocal可以在不同的線程之中互不干擾地存儲(chǔ)并提供數(shù)據(jù)谓罗,通過ThreadLocal可以輕松獲取每個(gè)線程的Looper,所以季二,ThreadLocal是理解Looper的關(guān)鍵之一檩咱。
先看一下官方對(duì)這個(gè)類的解釋
/**
* Implements a thread-local storage, that is, a variable for which each thread
* has its own value. All threads share the same {@code ThreadLocal} object,
* but each sees a different value when accessing it, and changes made by one
* thread do not affect the other threads. The implementation supports
* {@code null} values.
*
* @see java.lang.Thread
* @author Bob Lee
*/
大概意思就是說:實(shí)現(xiàn)了一個(gè)線程本地存儲(chǔ)揭措,也就是說,每個(gè)線程的一個(gè)變量刻蚯,有自己的值绊含。所有線程共享同一個(gè) ThreadLocal對(duì)象,但每個(gè)線程訪問它時(shí)炊汹,看到一個(gè)不同的值躬充,如果一個(gè)線程改變了這個(gè)值,不影響其他線程讨便,ThreadLocal支持NULL值充甚。理解好費(fèi)勁,只能說我翻譯的太差霸褒!
重新解釋一下:當(dāng)工作于多線程中的對(duì)象使用ThreadLocal 維護(hù)變量時(shí)伴找,ThreadLocal 為 每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本。每個(gè)線程獨(dú)立改變自己的副本废菱,而不影響其他線程所對(duì)應(yīng)的變量副本技矮。這就是它的基本原理,ThreadLocal主要的 API很簡(jiǎn)單昙啄。
public void set(T value):將值放入線程局部變量中
public T get():從線程局部變量中獲取值
public void remove():從線程局部變量中移除值(有助于 JVM 垃圾回收)
protected T initialValue():返回線程局部變量中的初始值(默認(rèn)為 null)
ThreadLocal為各個(gè)線程保存一個(gè)變量副本穆役,這句話是關(guān)鍵,怎么理解梳凛?先來一個(gè)簡(jiǎn)單的DEMO
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<Boolean>(){
@Override
protected Boolean initialValue() {
//初始值是false
return false;
}
};
mBooleanThreadLocal.set(true);
System.out.println("[Thread#main]mBooleanThreadLocal=" +mBooleanThreadLocal.hashCode()+" "+ mBooleanThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
System.out.println( "[Thread#1]mBooleanThreadLocal="+mBooleanThreadLocal.hashCode() +" "+ mBooleanThreadLocal.get());
};
}.start();
new Thread("Thread#2") {
@Override
public void run() {
System.out.println( "[Thread#2]mBooleanThreadLocal="+mBooleanThreadLocal.hashCode() + " "+mBooleanThreadLocal.get());
};
}.start();
}
}
輸出結(jié)果:
[Thread#main]mBooleanThreadLocal=366712642 true
[Thread#1]mBooleanThreadLocal=366712642 false
[Thread#2]mBooleanThreadLocal=366712642 false
在上面的代碼中耿币,在主線程中設(shè)置mBooleanThreadLocal的值為true,在子線程1中設(shè)置mBooleanThreadLocal的值為false韧拒,在子線程2中不設(shè)置mBooleanThreadLocal的值淹接,然后分別在3個(gè)線程中通過get方法去mBooleanThreadLocal的值,所以叛溢,主線程中應(yīng)該是true塑悼,子線程1中應(yīng)該是false,而子線程2中由于沒有設(shè)置值楷掉,所以應(yīng)該是初始值false厢蒜。
再看一個(gè)DEMO
public class ThreadLocalTest {
//創(chuàng)建一個(gè)Integer型的線程本地變量
public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];
for (int j = 0; j < 5; j++) {
threads[j] = new Thread(new Runnable() {
@Override
public void run() {
//獲取當(dāng)前線程的本地變量,然后累加5次
int num = local.get();
for (int i = 0; i < 5; i++) {
num++;
}
//重新設(shè)置累加后的本地變量
local.set(num);
System.out.println(Thread.currentThread().getName() + " : "+ local.get());
}
}, "Thread-" + j);
}
for (Thread thread : threads) {
thread.start();
}
}
}
輸出結(jié)果:
Thread-1 : 5
Thread-3 : 5
Thread-4 : 5
Thread-0 : 5
Thread-2 : 5
開了5個(gè)線程烹植,每個(gè)線程累加后的結(jié)果都是5斑鸦,各個(gè)線程處理自己的本地變量值,線程之間互不影響草雕。
讀到這里巷屿,相信你對(duì)ThreadLocal的作用已經(jīng)了解了,ThreadLocal和synchronized是有區(qū)別的墩虹,概括起來說嘱巾,對(duì)于多線程資源共享的問題憨琳,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式旬昭。前者僅提供一份變量篙螟,讓不同的線程排隊(duì)訪問,而后者為每一個(gè)線程都提供了一份變量问拘,因此可以同時(shí)訪問而互不影響闲擦,所以ThreadLocal和synchronized都能保證線程安全,但是應(yīng)用場(chǎng)景卻大不一樣场梆。
現(xiàn)在從源碼中去找找答案,為什么各個(gè)線程都能保留一份副本纯路,做到多并發(fā)的時(shí)候或油,線程互不影響呢?
ThreadLocal的構(gòu)造函數(shù)是空的驰唬,啥也沒有顶岸。
public ThreadLocal() {
}
看一下里面的set方法
/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
首先獲得了當(dāng)前線程實(shí)例,將實(shí)例傳入values方法中叫编,獲得一個(gè)Values對(duì)象辖佣,第一次獲得的Values對(duì)象是空的,就調(diào)用initializeValues初始化一個(gè)values對(duì)象搓逾,最后把當(dāng)前對(duì)象作為key,value作為值卷谈,放進(jìn)values中。現(xiàn)在的重點(diǎn)就是搞懂Values是什么霞篡?Values原來是ThreadLocal的靜態(tài)內(nèi)部類世蔗。在ThreadLocal.Values中有一個(gè)table成員,而這個(gè)table就以key,value的形式存儲(chǔ)了線程的本地變量朗兵。key是ThreadLocal<T>類型的對(duì)象的弱引用污淋,而Value則是線程需要保存的線程本地變量T。
/**
* 存放數(shù)據(jù)的數(shù)組余掖。他存放數(shù)據(jù)的形式有點(diǎn)像map,是ThreadLocal與value相對(duì)應(yīng), 長(zhǎng)度總是2的N次方
*/
private Object[] table;
Value的主要API有下面幾個(gè)
- void put(ThreadLocal<?> key, Object value):往table里添加一個(gè)鍵值對(duì)
- void add(ThreadLocal<?> key, Object value):也是往table里面添加鍵值對(duì)
- Object getAfterMiss(ThreadLocal<?> key):在首位置沒找到值的時(shí)候通過這個(gè)方法來找到給定key的值
-
void remove(ThreadLocal<?> key):刪掉給定key對(duì)應(yīng)的值
ThreadLocal.Values的其他成員
Values是調(diào)用put方法把傳進(jìn)來的鍵值對(duì)給存到table表里面去寸爆,這里不去分析它是怎么實(shí)現(xiàn)的了(可以移步http://blog.csdn.net/luoyanglizi/article/details/51510233)
/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
上面是ThreadLocal存的過程,現(xiàn)在看取的過程盐欺。
/**
* Returns the value of this variable for the current thread. If an entry
* doesn't yet exist for this variable on this thread, this method will
* create an entry, populating the value with the result of
* {@link #initialValue()}.
*
* @return the current value of the variable for the calling thread.
*/
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
首先取出當(dāng)前線程的Values對(duì)象赁豆,跟set方法一樣,如果這個(gè)對(duì)象為null那么就返回初始值找田, 如果Values對(duì)象不為null挑势,那就取出它的table數(shù)組并找出ThreadLocal的reference對(duì)象在table數(shù)組中的位置悉抵,然后table數(shù)組中的下一個(gè)位置所存儲(chǔ)的數(shù)據(jù)就是ThreadLocal的值。從可以看出档冬,ThreadLocal的set和get方法所操作的對(duì)象都是當(dāng)前線程的Values對(duì)象的table數(shù)組,如果一個(gè)變量使用了ThreadLocal,通過ThreadLocal的set和get方法,每一個(gè)線程的都會(huì)有一份這個(gè)變量的副本(鍵值對(duì)的形式進(jìn)行存惹陡佟),這就是為什么多線程并發(fā)的時(shí)候,線程互不影響的操作一個(gè)變量准谚。
最后在舉一個(gè)ThreadLocal的應(yīng)用的例子:JDBC連接mysql數(shù)據(jù)庫,把 Connection 放到了 ThreadLocal 中去扣,這樣每個(gè)線程之間就隔離了柱衔,不會(huì)相互干擾。
public class DBUtil {
// 數(shù)據(jù)庫配置
private static final String driver = "com.mysql.jdbc.Driver";
private static final String url = "jdbc:mysql://localhost:3306/demo";
private static final String username = "xxx";
private static final String password = "xxx";
// 定義一個(gè)用于放置數(shù)據(jù)庫連接的局部線程變量(使每個(gè)線程都擁有自己的連接)
private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>();
// 獲取連接
public static Connection getConnection() {
Connection conn = connContainer.get();
try {
if (conn == null) {
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connContainer.set(conn);
}
return conn;
}
// 關(guān)閉連接
public static void closeConnection() {
Connection conn = connContainer.get();
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connContainer.remove();
}
}
}
Please accept mybest wishes for your happiness and success
參考鏈接:
https://my.oschina.net/clopopo/blog/149368
http://blog.csdn.net/singwhatiwanna/article/details/48350919
https://my.oschina.net/huangyong/blog/159725