ThreadLocal源码分析
概述
1.ThreadLocal的介绍
2.ThreadLocal的使用
3.ThreadLocal源码分析
第1节 ThreadLocal介绍
ThreadLocal——线程本地变量副本
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本
每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal类接口很简单,常用方法如下:
1.public void set(T value)设置当前线程的线程局部变量的值。
2.public T get()方法返回当前线程所对应的线程局部变量。
3.public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,
该方法是JDK 5.0新增的方法。当线程结束后,对应该线程的局部变量将自动被垃圾回收,
所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
4.public static <S> ThreadLocal<S> withInitial()方法是JDK1.8新增的额方法。
创建一个线程本地变量副本。默认值由Supplier的get()方法控制。
5.protected T initialValue()方法的访问修饰符是protected,
该方法为第一次调用get方法提供一个初始值。默认情况下,
第一次调用get方法返回值null。在使用时,我们一般会重写ThreadLocal的initialValue方法,
使第一次调用get方法时返回一个我们设定的初始值。
第2节 ThreadLocal的使用
看下面ThreadLocal使用Demo
public class ThreadLocalDemo {
/**
* 定义了一个ThreadLocal<Integer>对象,
* 并重写它的initialValue方法,初始值是3
* 这个对象会在三个线程间共享
*/
private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 3);
/**
* 设置一个信号量,许可数为1,让三个线程顺序执行
*/
private Semaphore semaphore = new Semaphore(1);
/**
* 一个随机数
*/
private Random random = new Random();
/**
* 每个线程中调用这个对象的get方法,再调用一个set方法设置一个随机值
*/
public class Worker implements Runnable {
@Override
public void run() {
try {
// 随机延时1s以内的时间
Thread.sleep(random.nextInt(1000));
// 获取许可
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 从threadLocal中获取值
int value = threadLocal.get();
System.out.println(Thread.currentThread().getName() + " threadLocal old value : " + value);
// 修改value值
value = random.nextInt();
// 新的value值放入threadLocal中
threadLocal.set(value);
System.out.println(Thread.currentThread().getName() + " threadLocal new value: " + value);
System.out.println(Thread.currentThread().getName() + " threadLocal latest value : " + threadLocal.get());
// 释放信号量
semaphore.release();
// 在线程池中,当线程退出之前一定要记得调用remove方法,因为在线程池中的线程对象是循环使用的
threadLocal.remove();
}
}
/**
* 创建三个线程,每个线程都会对ThreadLocal对象进行操作
*/
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
es.execute(threadLocalDemo.new Worker());
es.execute(threadLocalDemo.new Worker());
es.execute(threadLocalDemo.new Worker());
es.shutdown();
}
}
执行结果如下。
pool-1-thread-3 threadLocal old value : 3
pool-1-thread-3 threadLocal new value: -189935755
pool-1-thread-3 threadLocal latest value : -189935755
pool-1-thread-2 threadLocal old value : 3
pool-1-thread-2 threadLocal new value: -180783580
pool-1-thread-2 threadLocal latest value : -180783580
pool-1-thread-1 threadLocal old value : 3
pool-1-thread-1 threadLocal new value: -1299325901
pool-1-thread-1 threadLocal latest value : -1299325901
第3节 ThreadLocal源码分析
ThreadLocal内部结构
/*TheadLocal中的set()源码分析*/
/**
* 设置ThreadLocal变量的值
*/
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果当前线程的ThreadLocalMap非空
if (map != null)
// 往ThreadLocalMap中添加K-V
map.set(this, value);
else
// 如果当前线程的ThreadLocalMap为空
// 创建ThreadLocalMap对象
createMap(t, value);
}
ThreadLocalMap map = getMap(t);是获取当前线程内部的ThreadLocalMap对象。
/**
* 返回与当前的线程对象相关的ThreadLocalMap对象
*/
ThreadLocalMap getMap(Thread t) {
// 返回线程t的threadLocals对象
return t.threadLocals;
}
Thread类的内部有个属性ThreadLocalMap属性。
/* Thread类的属性 */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap的getEntry()方法如下。
/**
* 获取key对应的entry
*/
private Entry getEntry(ThreadLocal<?> key) {
// key的hashcode & 1111,即保留key的hashcode的低4位
int i = key.threadLocalHashCode & (table.length - 1);
// 获取hash表位置i处的Entry对象
Entry e = table[i];
// e非空且e的key等于key,说明就是要查找的Entry对象
if (e != null && e.get() == key)
// 返回Entry对象e
return e;
else
// 没有找到对应的Entry,需要继续查找
return getEntryAfterMiss(key, i, e);
}
getEntryAfterMiss()方法如下。
/**
* 直接在hash表对应的槽位上没有找到对饮的Entry
* 采用线性探测法继续在hash表上查找
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
// hash表的Entry数组
Entry[] tab = table;
// Entry数组的长度
int len = tab.length;
// 如果e非空
while (e != null) {
// 获取e的key
ThreadLocal<?> k = e.get();
// 如果k等于key,找到对应的Entry
if (k == key)
// 返回Entry对象
return e;
// 如果k为空
if (k == null)
// 移除这个失效的Entry对象
expungeStaleEntry(i);
else
// 修改i为hash表的下一个位置
i = nextIndex(i, len);
// 修改e为tab[i],进入下一次循环
e = tab[i];
}
// 没有找到对应的Entry对象
return null;
}
expungeStaleEntry()方法如下。
/**
* 清理失效的Entry
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清理staleSlot位置的Entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 调整hash表直到后面第一个tab[i]为null为止
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果key为null,将该entry置为null
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 下面是新计算一下hash值,如果位置与当前位置不同
// 需要重新找一个位置放该节点。用的也是线性探测法
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}