前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal 源码分析

ThreadLocal 源码分析

作者头像
Yano_nankai
发布2018-12-24 10:40:26
2480
发布2018-12-24 10:40:26
举报
文章被收录于专栏:二进制文集二进制文集

Demo

代码语言:javascript
复制
public class ThreadLocalMain {

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("hello, main");
        System.out.println(threadLocal.get());

        ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
        threadLocal2.set("hello, main2");
        System.out.println(threadLocal2.get());
    }
}

输出

代码语言:javascript
复制
hello, main
hello, main2

源码分析

ThreadLocal 类上的注释:

代码语言:javascript
复制
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.
   import java.util.concurrent.atomic.AtomicInteger;
  
   public class ThreadId {
       // Atomic integer containing the next thread ID to be assigned
       private static final AtomicInteger nextId = new AtomicInteger(0);
  
       // Thread local variable containing each thread's ID
       private static final ThreadLocal<Integer> threadId =
           new ThreadLocal<Integer>() {
               @Override protected Integer initialValue() {
                   return nextId.getAndIncrement();
           }
       };
  
       // Returns the current thread's unique ID, assigning it if necessary
       public static int get() {
           return threadId.get();
       }
   }
   
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

成员变量

ThreadLocal类中有一个threadLocalHashCode,作为ThreadLocalMap中key(ThreadLocal)的哈希值。

代码语言:javascript
复制
private final int threadLocalHashCode = nextHashCode();

ThreadLocalMap是ThreadLocal的内部类,是一个自定义的HashMap。注意ThreadLocalMap和HashMap实现原理有很大差别:

  1. HashMap是取key对象的哈希值,ThreadLocalMap是取key对象(ThreadLocal)的threadLocalHashCode变量
  2. 解决hash冲突:HashMap是通过链表法,ThreadLocalMap是通过开放地址法。

其set核心代码如下,for循环中对table[i]进行判断,key是该ThreadLocal则修改其value,否则其为null时再放入。

代码语言:javascript
复制
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

set

获取当前线程的ThreadLocalMap,对其进行set操作。

代码语言:javascript
复制
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

get

代码语言:javascript
复制
    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();
    }

ThreadLocal 是否会内存泄漏?

会。ThreadLocal中table是一个Entry对象的数组,而Entry是一个ThreadLocal的弱引用。

代码语言:javascript
复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

当 Key 为 null 时,该条目就变成“废弃条目”,相关“value”的回收,往往依赖于几个关键点,即 set、remove、rehash。

弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

ThreadLocal内存泄漏(其实不算是内存泄漏,只能算是内存不足而已)的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。(参考自:深入分析 ThreadLocal 内存泄漏问题

ThreadLocal 的最佳实践

废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应 ThreadLocalMap!应用一定要自己负责 remove,并且不要和线程池配合。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.12.23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Demo
  • 源码分析
    • 成员变量
      • set
        • get
        • ThreadLocal 是否会内存泄漏?
        • ThreadLocal 的最佳实践
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档