在併發編程中,爲了線性安全我們經常要使用各種各樣的“鎖”。 不管鎖的粒度如何小,哪怕是CAS操作,都存在先來後到的排隊問題。有時候我們只是單純地計數,沒有太複雜的操作,想要一種不涉及鎖的線程安全的操作——ThreadLocal就是一種無鎖的線程安全的設計。
當使用ThreadLocal修飾變量時,ThreadLocal爲每個線程(thread)設置了一個獨立的局部(local)變量副本,每個線程的變量副本互不干擾,從而實現了線程安全性。
一、ThreadLocal使用
我們創建一個ThreadLocal變量count,用兩個不同的線程使用同一個變量,並且一個快一個慢(sleep100ms 和sleep 200ms)看它們能否各自獨立計數。
public class MainTest {
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() { //重寫了initialValue 爲了給count重初值
return new Integer(0);
}
};
public static int getNext(){
int data = count.get() + 1;
count.set(data);
return data;
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
System.out.println(Thread.currentThread().getName() + " " + getNext());
Thread.sleep(100); //這個線程每次sleep 100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
System.out.println(Thread.currentThread().getName() + " " + getNext());
Thread.sleep(200); //這個線程每次sleep 200毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
運行結果
可以看到兩個線程使用了同一個ThreadLocal變量,各自獨立計數一個快一個慢,且沒有出現線程安全性問題。
ThreadLocal使用時要先用set()再用get(), 因爲默認的initialValue()裏value的值設置爲null,如果不先set()就要重寫initialValue()方法,否則會拋nullPointerException。
二、原理與源碼解析
ThreadLocal<T> 類有一個靜態內部類ThreadLocalMap,ThreadLocalMap裏有一個Entry內部類組成的數組。
ThreadLocal通過get()與set()方法來獲取和存儲變量數據到數組中。
set()方法
當一個線程中調用了ThreadLocal的set方法時,首先獲取本線程的threadID,然後獲取本線程內部的ThreadLocalMap,用ThreadLocal值和value創始一個Entry對象存入自身所在的ThreadLocalMap中。因此,各個線程的ThreadLocal和value值都存儲在各線程內部,互不干擾。
get()方法
get方法與set方法類似。當一個線程調用了ThreadLocal的get方法時,獲取本線程的threadID和本線程內部的ThreadLocalMap,然後取出Entry對象獲存儲的值。也是各線程互不干擾。