如果看懂了ThreadLocal的set()方法,get()、remove()方法也就好理解了,所以重点看一下set()方法。
1.获取当前线程对象
2.获取当前线程对象的成员变量ThreadLocalMap
3.1不为null,set值
3.1.1获取当前ThreadLocalMap对象的Entry数组
3.1.2获取ThreadLocal对象的i值
3.1.3遍历Entry数组,根据ThreadLocal对象的i值判断,如果ThreadLocal对象已存在数组中,更新值,退出
3.1.4不存在数组中,以key-value形式:将ThreadLocal对象与值value绑定放入数组中
3.2为null,为当前线程创建成员变量ThreadLocalMap
3.2.1 new一个Entry数组,初始容量为INITIAL_CAPACITY=16
3.2.2计算出存放在Entry数组的位置i:ThreadLocal的threadLocalHashCode与15的位运算,结果与取模相同。
3.2.3放入Entry数组
源码分析如下:
public class Thread implements Runnable {
//每个线程对象都维护了一个成员变量ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal {
private final int threadLocalHashCode = nextHashCode();
public T get() {}
public void remove() {}
public void set(T value) {
Thread t = Thread.currentThread();//当前线程对象
ThreadLocal.ThreadLocalMap map = getMap(t);//获取当前线程对象的成员变量ThreadLocalMap
if (map != null)
map.set(this, value);//不为null,set值
else
createMap(t, value);//为null,为当前线程创建ThreadLocalMap
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);//为线程对象创建成员变量ThreadLocalMap
}
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
static class Entry extends WeakReference> {
//线程实际存储私有数据的地方
Object value;
//以key-value形式,将ThreadLocal与Object对象绑定。一个ThreadLocal对象在一个线程Thread对象中只可存储一个Object。
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
//ThreadLocalMap类内部维护了一个Entry数组对象table
private ThreadLocal.ThreadLocalMap.Entry[] table;
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];//new一个Entry数组,初始容量为INITIAL_CAPACITY=16
/**
* 计算出存放在table数组的位置:ThreadLocal的threadLocalHashCode与15的位运算,结果与取模相同。
* 因为每个ThreadLocal对象创建后,threadLocalHashCode是final不变的,所以每个ThreadLocal对象的i值是不变的,
* 这也是为什么一个ThreadLocal对象在一个线程Thread对象中只可存储一个Object的原因
*/
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);//放入Entry数组
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;//当前ThreadLocalMap对象的Entry数组
int len = tab.length;
int i = key.threadLocalHashCode & (len - 1);//ThreadLocal对象的i值
//遍历Entry数组
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
//ThreadLocal对象已存在数组中,更新值,退出
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//不存在数组中,以key-value形式:将ThreadLocal与value绑定放入数组中
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
}
}
根据上面源码总结如下:
一个Thread对象可有多个ThreadLocal对象在线程的本地内存中存储值,一个ThreadLocal对象可在多个线程的本地内存中存储值。所以Thread与ThreadLocal可简单理解为多对多关系。(线程的本地内存也称为线程的工作内存,JMM内存模型定义的概念)
ThreadLocal的本质就是一个工具类,提供了set()、get()、remove()等方法,提供对线程本地内存的值的操作。Thread的成员变量ThreadLocalMap实现线程本地内存数据存储的(重点:成员变量实现本地内存对象的存储),ThreadLocalMap是定义在ThreadLocal的静态内部类。
ThreadLocalMap维护一个Entry类型数组,Entry是定义在ThreadLocalMap的静态内部类,Entry就是用了一个Object类型的成员变量value来存储值的,而且Entry的构造函数还将本ThreadLocal对象与线程值value以key-value形式绑定了。一个ThreadLocal对应Entry[]数组的下标i是唯一不变的,因为ThreadLocal的hash值是final。
Synchronized和ThreadLocal都是用来解决多线程并发数据访问的,但是Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。所以,使用场景要根据这个变量本身是否应该是共享的!
ThreadLocal常用使用场景为解决数据库连接、Session管理等。可参考spring的管理。
例子:一个变量是成员变量(多线程共享的),但它本质上应该是线程私有的,就可用ThreadLocal将共享成员变量copy一份到线程私有变量中。如下Demo:
/**
* 问题:如何做到 MyThreadLocal单例,sendMessage()方法不加锁,但是多线程执行sendMessage()仍然是线程安全的?
* 答:共享成员变量connection设为线程私有。即使用ThreadLocal将共享成员变量变成线程私有
**/
public class MyThreadLocal {
//成员变量,多线程共享
private Connection connection;
public static ThreadLocal threadLocal = new ThreadLocal();
public void sendMessage() throws SQLException {
//Statement statement = connection.createStatement();
Statement statement = getConnection().createStatement();
//...
}
public static Connection getConnection() {
Connection conn = threadLocal.get();//去当前线程中获取value对象
if (conn == null) {
Connection conn = ConnectionManager.getConnection();
threadLocal.set(conn);
return conn;
} else {
return conn;
}
}
}