什么是ThreadLocal:
可以把它理解成一個(gè)容器,用于存放線程的局部變量书斜,它為每個(gè)線程提供了一個(gè)獨(dú)立的副本
使用場景:
一般來說牡借,當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時(shí)候,就可以考慮采用ThreadLocal
ThreadLocal的使用:
API如下:
- public void set(T value):將值放入線程局部變量中
- public T get():從線程局部變量中獲取值
- public void remove():從線程局部變量中移除值
- protected T initialValue():返回線程局部變量中的初始值
例子:
(1)沒有使用ThreadLocal:
public interface BusBase {
int getPassengerNum();
}
public class Bus implements BusBase{
private static int passengerNum = 0;
public int getPassengerNum() {
passengerNum = passengerNum + 1;
return passengerNum;
}
}
public class TestThread extends Thread {
private BusBase bus;
public TestThread(BusBase bus) {
this.bus = bus;
}
@Override
public void run() {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName() + " => " + bus.getPassengerNum());
}
}
}
public class WithoutThreadLocalTest {
public static void main(String[] args) {
Bus studentBus = new Bus();
TestThread thread1 = new TestThread(studentBus);
TestThread thread2 = new TestThread(studentBus);
TestThread thread3 = new TestThread(studentBus);
thread1.start();
thread2.start();
thread3.start();
}
}
控制臺(tái)打印結(jié)果如下:
Thread-2 => 3
Thread-0 => 2
Thread-0 => 5
Thread-1 => 1
Thread-1 => 6
Thread-2 => 4
可以看到雨饺,所有線程共用了一個(gè)passengerNum變量
(2)使用了ThreadLocal:
public class BusWithThreadLocal implements BusBase{
private static ThreadLocal<Integer> passengerNum = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public int getPassengerNum() {
passengerNum.set(passengerNum.get() + 1);
return passengerNum.get();
}
}
public class ThreadLocalTest {
public static void main(String[] args) {
BusWithThreadLocal studentBus = new BusWithThreadLocal();
TestThread thread1 = new TestThread(studentBus);
TestThread thread2 = new TestThread(studentBus);
TestThread thread3 = new TestThread(studentBus);
thread1.start();
thread2.start();
thread3.start();
}
}
控制臺(tái)打印結(jié)果如下:
Thread-0 => 1
Thread-0 => 2
Thread-1 => 1
Thread-1 => 2
Thread-2 => 1
Thread-2 => 2
從結(jié)果可以看到钳垮,每個(gè)線程中的passengerNum變量的值互不干擾,一下子變得線程安全了
通過源碼(基于jdk 1.8)分析原理:
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);
}
set方法中通過getMap()方法獲得了當(dāng)前線程的ThreadLocalMap對象额港,getMap方法具體如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到饺窿,getMap方法就是返回了當(dāng)前線程t的threadLocals變量,而threadLocals變量是ThreadLocalMap的一個(gè)對象移斩,那么ThreadLocalMap又是啥短荐,源碼如下:
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap是ThreadLocal的一個(gè)靜態(tài)內(nèi)部類倚舀,從注釋可以看出它是用來保存線程本地變量的一個(gè)hashMap,它的key是ThreadLocal變量忍宋,value即為對應(yīng)的保存值痕貌。
看到這里該明白了吧,每個(gè)threadlocal通過set設(shè)置的值糠排,最后是保存在了調(diào)用時(shí)線程的一個(gè)hashMap里面舵稠,這個(gè)hashMap通過threadlocal對象作為key來標(biāo)識(shí)保存的值
get()方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
可以看到,get方法正是從當(dāng)前線程t的threadLocals變量(ThreadLocalMap)中入宦,通過ThreadLocal對象索引哺徊,得到了之前保存下來的value