目录
- 1. 是什么
- 2. 如何使用
- 3. 原理分析
- 3.1. uml
- 3.2. 构造方法
- 3.3. set方法
- 3.3.1. 先获取Thread对应的ThreadLocalMap
- 3.3.2. 有的话调用ThreadLocalMap set方法插入ThreadLocal:value
- 3.3.2.1. 发生Hash冲突则使用开放寻址法
- 3.3.3. 没有则创建ThreadLocalMap并放入ThreadLocal:value
- 3.3.3.1. ThreadLocalMap构造方法
- 3.4. get方法
- 3.4.1. 先获取Thread对应的ThreadLocalMap
- 3.4.2. Map不为空,那么从Map中获取key为当前ThreadLocal对象的Entry
- 3.4.2.1. 使用开放寻址法继续寻找下一个位置
- 3.4.3. Map为空,则向创建后初始化默认值Null
- 3.5. remove方法
- 3.5.1. 先获取Thread对应的ThreadLocalMap
- 3.5.2. 通过ThreadLocalMap remove key为当前ThreadLocal的Entry
- 4. 总结
- 5. 参考
1. 是什么
不是线程同步机制,是一种线程数据隔离机制。
多线程共享变量通信的情况下,我们需要保证线程安全。一种方法是使用锁,另一种就是数据隔离机制。
ThreadLocal用的是后一种,即每个线程操作的是自己独有的数据,因此互相之间不会影响
2. 如何使用
- public class ThreadLocalTest
- {
- public static void main(String[] args) throws InterruptedException
- {
- ThreadLocal<String> threadLocal = new ThreadLocal<>();
- threadLocal.set("main");
- System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());//main:main
- Thread thread1 = new Thread(()->{
- threadLocal.set("thread1");
- System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());//Thread-0:thread1
- });
- Thread thread2 = new Thread(()->{
- threadLocal.set("thread2");
- System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());//Thread-1:thread2
- });
- thread1.start();
- thread2.start();
- thread1.join();
- thread2.join();
- System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());//main:main
- threadLocal.remove();
- System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());//main:null
- }
- }
复制代码 3. 原理分析
3.1. uml
Thread有一个ThreadLocalMap属性,
ThreadLocalMap有一个Entry数组属性
Entry有key和value属性,其中key为ThreadLocal,value为Object。并且Entry继承了WeakReference
3.2. 构造方法
- //记录hash值
- private static AtomicInteger nextHashCode =
- new AtomicInteger();
-
- public ThreadLocal() {
- }
复制代码 3.3. set方法
- public void set(T value) {
- Thread t = Thread.currentThread();
- //获取当前线程对应的ThreadLocalMap属性
- ThreadLocalMap map = getMap(t);
- if (map != null)
- //将(ThreadLocal,value)构造成Entry放入ThreadLocalMap
- map.set(this, value);
- else
- //创建新的map关联当前线程,并且并放入(ThreadLocal,value)
- createMap(t, value);
- }
复制代码
- 4行:先获取Thread对应的ThreadLocalMap
- 5-7行:有的话调用ThreadLocalMap set方法插入ThreadLocal:value
- 8-10行:没有则创建ThreadLocalMap并放入ThreadLocal:value
下面具体分析:
3.3.1. 先获取Thread对应的ThreadLocalMap
- Thread t = Thread.currentThread();
- //获取当前线程对应的ThreadLocalMap属性
- ThreadLocalMap map = getMap(t);
复制代码- ThreadLocalMap getMap(Thread t) {
- //获取当前Thread对应的ThreadLocalMap对象
- return t.threadLocals;
- }
复制代码 3.3.2. 有的话调用ThreadLocalMap set方法插入ThreadLocal:value
- private void set(ThreadLocal<?> key, Object value) {
- Entry[] tab = table;
- int len = tab.length;
- //计算当前元素在数组中的位置
- int i = key.threadLocalHashCode & (len-1);
- //如果key不在数组中,那么一直找到空位置
- for (Entry e = tab[i];
- e != null;
- //开放寻址法
- e = tab[i = nextIndex(i, len)]) {
- ThreadLocal<?> k = e.get();
- //key相等,替换值
- if (k == key) {
- e.value = value;
- return;
- }
- //key为空,那么构造entry放入
- if (k == null) {
- replaceStaleEntry(key, value, i);
- return;
- }
- }
- //走到这里说明退出循环,找到了空位置
- tab[i] = new Entry(key, value);
- int sz = ++size;
- //是否需要扩容
- if (!cleanSomeSlots(i, sz) && sz >= threshold)
- rehash();
- }
复制代码 3.3.2.1. 发生Hash冲突则使用开放寻址法
- private static int nextIndex(int i, int len) {
- //线性探测i+1的位置
- return ((i + 1 < len) ? i + 1 : 0);
- }
复制代码 3.3.3. 没有则创建ThreadLocalMap并放入ThreadLocal:value
- else
- //创建新的map关联当前线程,并且并放入(ThreadLocal,value)
- createMap(t, value);
复制代码- void createMap(Thread t, T firstValue) {
- //当前ThreadLocal对象作为key,value作为value构造entry并构造map
- //thread的ThreadLocalMap属性是这里设置的
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
复制代码 3.3.3.1. ThreadLocalMap构造方法
- static class ThreadLocalMap {
- //由于Entry继承了WeakRefence
- static class Entry extends WeakReference<ThreadLocal<?>> {
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- //key是个弱引用(即一旦发生垃圾回收就会回收这个引用指向的对象)。
- //当key被回收后,也即ThreadLocal被回收了,但是Entry中value的引用还在无法回收,可能会造成内存泄漏
- super(k);
- value = v;
- }
- }
- //初始容量为16,必须为2的次方
- private static final int INITIAL_CAPACITY = 16;
- //使用数组+线性探测法
- private Entry[] table;
- //实际使用的长度
- private int size = 0;
- //实际使用的长度达到这个长度时需要扩容
- private int threshold;
-
- ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
- table = new Entry[INITIAL_CAPACITY];
- //计算下标并且存入entry
- int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
- table[i] = new Entry(firstKey, firstValue);
-
- size = 1;
- setThreshold(INITIAL_CAPACITY);//设置threshold为16*2/3=10
- }
- private void setThreshold(int len) {
- threshold = len * 2 / 3;
- }
复制代码 3.4.3. Map为空,则向创建后初始化默认值Null
- public T get() {
- Thread t = Thread.currentThread();
- //获取当前线程对应的ThreadLocalMap
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- //以当前ThreadLocal对象作为key从ThreadLocalMap中获取entry
- ThreadLocalMap.Entry e = map.getEntry(this);
- //从entry中获取value并返回
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- //初始化map
- return setInitialValue();
- }
复制代码 3.5. remove方法
- Thread t = Thread.currentThread();
- //获取当前线程对应的ThreadLocalMap属性
- ThreadLocalMap map = getMap(t);
复制代码
- 3行:先获取Thread对应的ThreadLocalMap
- 4-5行:通过ThreadLocalMap remove key为当前ThreadLocal的Entry
3.5.1. 先获取Thread对应的ThreadLocalMap
- ThreadLocalMap getMap(Thread t) {
- //获取当前Thread对应的ThreadLocalMap对象
- return t.threadLocals;
- }
复制代码- ThreadLocalMap getMap(Thread t) {
- //获取当前Thread对应的ThreadLocalMap对象
- return t.threadLocals;
- }
复制代码 3.5.2. 通过ThreadLocalMap remove key为当前ThreadLocal的Entry
- private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
- Entry[] tab = table;
- int len = tab.length;
- //一直线性探测到为null(不存在)
- while (e != null) {
- ThreadLocal<?> k = e.get();
- //key是否相等
- if (k == key)
- return e;
- //ThreadLocal被回收了,那么回收value对象
- if (k == null)
- expungeStaleEntry(i);
- else//继续下一个
- i = nextIndex(i, len);
- e = tab[i];
- }
-
- return null;
- }
复制代码 }
4. 总结
- 每个Thread都有ThreadLocalMap属性。可以想象成一个HashMap,这个Map中Entry 的key为ThreadLocal,value为要存储的值
- 由于key为ThreadLocal自己,因此一个ThreadLocal只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal
- 由于Entry继承了WeakRefence,key是个弱引用(即一旦发生垃圾回收就会回收这个引用指向的对象)。当key被回收后,也即ThreadLocal被回收了,但是Entry中value的引用还在无法回收,可能会造成内存泄漏。因此我们需要用static引用ThreadLocal变量且手动remove
5. 参考
- 手撕面试题ThreadLocal!!! | 并发编程网 – ifeve.com
- Java面试必问,ThreadLocal终极篇 - 掘金
- ThreadLocal内存泄漏问题 - 掘金
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |