ThreadLocal哨鸭,直譯為“線程本地”或“本地線程”民宿,如果你真的這么認(rèn)為,那就錯(cuò)了像鸡!其實(shí)活鹰,它就是一個(gè)容器,用于存放線程的局部變量,我認(rèn)為應(yīng)該叫做 ThreadLocalVariable(線程局部變量)才對志群,真不理解為什么當(dāng)初 Sun 公司的工程師這樣命名着绷。
早在 JDK 1.2 的時(shí)代,java.lang.ThreadLocal 就誕生了锌云,它是為了解決多線程并發(fā)問題而設(shè)計(jì)的荠医,只不過設(shè)計(jì)得有些難用,所以至今沒有得到廣泛使用桑涎。其實(shí)它還是挺有用的子漩,不相信的話,我們一起來看看這個(gè)例子吧石洗。
一個(gè)序列號生成器的程序幢泼,可能同時(shí)會(huì)有多個(gè)線程并發(fā)訪問它,要保證每個(gè)線程得到的序列號都是自增的讲衫,而不能相互干擾缕棵。
先定義一個(gè)接口:
public
interface
Sequence {
int
getNumber();
}
每次調(diào)用 getNumber() 方法可獲取一個(gè)序列號,下次再調(diào)用時(shí)涉兽,序列號會(huì)自增招驴。
再做一個(gè)線程類:
public
class
ClientThread
extends
Thread {
private
Sequence sequence;
public
ClientThread(Sequence sequence) {
this``.sequence = sequence;
}
@Override
public
void
run() {
for
(``int
i =
0``; i <
3``; i++) {
System.out.println(Thread.currentThread().getName() +
" => "
+ sequence.getNumber());
}
}
}
引用地址:http://www.importnew.com/20147.html
在線程中連續(xù)輸出三次線程名與其對應(yīng)的序列號。
我們先不用 ThreadLocal枷畏,來做一個(gè)實(shí)現(xiàn)類吧别厘。
public
class
SequenceA
implements
Sequence {
private
static
int
number =
0``;
public
int
getNumber() {
number = number +
1``;
return
number;
}
public
static
void
main(String[] args) {
Sequence sequence =
new
SequenceA();
ClientThread thread1 =
new
ClientThread(sequence);
ClientThread thread2 =
new
ClientThread(sequence);
ClientThread thread3 =
new
ClientThread(sequence);
thread1.start();
thread2.start();
thread3.start();
}
}
|
序列號初始值是0,在 main() 方法中模擬了三個(gè)線程拥诡,運(yùn)行后結(jié)果如下:
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 4
Thread-2 => 5
Thread-2 => 6
Thread-1 => 7
Thread-1 => 8
Thread-1 => 9
由于線程啟動(dòng)順序是隨機(jī)的触趴,所以并不是0、1渴肉、2這樣的順序冗懦,這個(gè)好理解。為什么當(dāng) Thread-0 輸出了1仇祭、2披蕉、3之后,而 Thread-2 卻輸出了4乌奇、5没讲、6呢?線程之間竟然共享了 static 變量礁苗!這就是所謂的“非線程安全”問題了爬凑。
那么如何來保證“線程安全”呢?對應(yīng)于這個(gè)案例寂屏,就是說不同的線程可擁有自己的 static 變量贰谣,如何實(shí)現(xiàn)呢娜搂?下面看看另外一個(gè)實(shí)現(xiàn)吧迁霎。
public
class
SequenceB
implements
Sequence {
private
static
ThreadLocal<Integer> numberContainer =
new
ThreadLocal<Integer>() {
@Override
protected
Integer initialValue() {
return
0``;
}
};
public
int
getNumber() {
numberContainer.set(numberContainer.get() +
1``);
return
numberContainer.get();
}
public
static
void
main(String[] args) {
Sequence sequence =
new
SequenceB();
ClientThread thread1 =
new
ClientThread(sequence);
ClientThread thread2 =
new
ClientThread(sequence);
ClientThread thread3 =
new
ClientThread(sequence);
thread1.start();
thread2.start();
thread3.start();
}
}
|
通過 ThreadLocal 封裝了一個(gè) Integer 類型的 numberContainer 靜態(tài)成員變量吱抚,并且初始值是0。再看 getNumber() 方法考廉,首先從 numberContainer 中 get 出當(dāng)前的值秘豹,加1,隨后 set 到 numberContainer 中昌粤,最后將 numberContainer 中 get 出當(dāng)前的值并返回既绕。
是不是很惡心?但是很強(qiáng)大涮坐!確實(shí)稍微饒了一下凄贩,我們不妨把 ThreadLocal 看成是一個(gè)容器,這樣理解就簡單了袱讹。所以疲扎,這里故意用 Container 這個(gè)單詞作為后綴來命名 ThreadLocal 變量。
運(yùn)行結(jié)果如何呢捷雕?看看吧椒丧。
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
每個(gè)線程相互獨(dú)立了,同樣是 static 變量救巷,對于不同的線程而言壶熏,它沒有被共享,而是每個(gè)線程各一份浦译,這樣也就保證了線程安全棒假。 也就是說,TheadLocal 為每一個(gè)線程提供了一個(gè)獨(dú)立的副本精盅!
搞清楚 ThreadLocal 的原理之后淆衷,有必要總結(jié)一下 ThreadLocal 的 API,其實(shí)很簡單渤弛。
- public void set(T value):將值放入線程局部變量中
- public T get():從線程局部變量中獲取值
- public void remove():從線程局部變量中移除值(有助于 JVM 垃圾回收)
- protected T initialValue():返回線程局部變量中的初始值(默認(rèn)為 null)
為什么 initialValue() 方法是 protected 的呢祝拯?就是為了提醒程序員們,這個(gè)方法是要你們來實(shí)現(xiàn)的她肯,請給這個(gè)線程局部變量一個(gè)初始值吧佳头。
了解了原理與這些 API,其實(shí)想想 ThreadLocal 里面不就是封裝了一個(gè) Map 嗎晴氨?自己都可以寫一個(gè) ThreadLocal 了康嘉,嘗試一下吧。
public
class
MyThreadLocal<T> {
private
Map<Thread, T> container = Collections.synchronizedMap(``new
HashMap<Thread, T>());
public
void
set(T value) {
container.put(Thread.currentThread(), value);
}
public
T get() {
Thread thread = Thread.currentThread();
T value = container.get(thread);
if
(value ==
null
&& !container.containsKey(thread)) {
value = initialValue();
container.put(thread, value);
}
return
value;
}
public
void
remove() {
container.remove(Thread.currentThread());
}
protected
T initialValue() {
return
null``;
}
}
|
以上完全山寨了一個(gè) ThreadLocal籽前,其中中定義了一個(gè)同步 Map(為什么要這樣亭珍?請讀者自行思考)敷钾,代碼應(yīng)該非常容易讀懂。
下面用這 MyThreadLocal 再來實(shí)現(xiàn)一把看看肄梨。
public
class
SequenceC
implements
Sequence {
private
static
MyThreadLocal<Integer> numberContainer =
new
MyThreadLocal<Integer>() {
@Override
protected
Integer initialValue() {
return
0``;
}
};
public
int
getNumber() {
numberContainer.set(numberContainer.get() +
1``);
return
numberContainer.get();
}
public
static
void
main(String[] args) {
Sequence sequence =
new
SequenceC();
ClientThread thread1 =
new
ClientThread(sequence);
ClientThread thread2 =
new
ClientThread(sequence);
ClientThread thread3 =
new
ClientThread(sequence);
thread1.start();
thread2.start();
thread3.start();
}
}
|
以上代碼其實(shí)就是將 ThreadLocal 替換成了 MyThreadLocal阻荒,僅此而已,運(yùn)行效果和之前的一樣众羡,也是正確的侨赡。
其實(shí) ThreadLocal 可以單獨(dú)成為一種設(shè)計(jì)模式,就看你怎么看了粱侣。
ThreadLocal 具體有哪些使用案例呢羊壹?
我想首先要說的就是:通過 ThreadLocal 存放 JDBC Connection,以達(dá)到事務(wù)控制的能力齐婴。
如何實(shí)現(xiàn)呢油猫?下回分解!
ThreadLocal 那點(diǎn)事兒(續(xù)集)
注意:當(dāng)您在一個(gè)類中使用了 static 成員變量的時(shí)候柠偶,一定要多問問自己情妖,這個(gè) static 成員變量需要考慮“線程安全”嗎?(也就是說嚣州,多個(gè)線程需要獨(dú)享自己的 static 成員變量嗎鲫售?)如果需要考慮,那就請用 ThreadLocal 吧该肴!