ThreadLocal详解

本文深入剖析了ThreadLocal的工作原理,包括其内部实现机制ThreadLocalMap及其Entry类,详细解读了set、get及remove方法的源码,并探讨了内存泄漏的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.简介

ThreadLocal 顾名思义 线程本地变量的意思

适用于每个线程需要自己独立的实例该实例需要在 当前线程的 多个方法 中被使用,也即变量在线程间隔离而在方法或类间共享的场景。切记 ThreadLocal  是某个变量某个线程生命周期内多个方法共享。而不是多个Thread共享这个变量。

 

 

 

 

 

 

二.源码

 

设置值源码

set方法源码

public void set(T value) {

    //获取当前线程
    Thread t = Thread.currentThread();

    //然后根据当前线程 获取ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);

    if (map != null)//ThreadLocalMap对象不为空 ThreadLocalMap set值
        map.set(this, value);
    else//ThreadLocalMap对象为空 创建ThreadLocalMap对象
        createMap(t, value);
}

首先,获取当前线程。然后根据当前线程 获取ThreadLocalMap对象。

 

获取ThreadLocalMap对象

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

即 返回Thread类threadLocals对象。那么这个对象是什么呢?

Thread类中

ThreadLocal.ThreadLocalMap threadLocals = null;

且Thread类中threadLocals 变量没有赋值的地方。也就是说这个值首次使用肯定是空。那么这个变量什么时候赋值呢?下面讲解。

因为首次获取的threadLocals对象为空,即ThreadLocalMap map为空。所以创建一个ThreadLocalMap。

 

创建ThreadLocalMap对象

/**
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

也就是说创建了一个ThreadLocalMap对象。并将创建的ThreadLocalMap对象赋值给Thread类的变量。

ThreadLocal.ThreadLocalMap threadLocals = null;

那么ThreadLocalMap这个类结构是怎样的那?是不是和HashMap一样呢?下面看

 

ThreadLocalMap类部分源码

static class ThreadLocalMap {

      
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

    /**
     * The initial capacity -- MUST be a power of two.
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;

    /**
     * The number of entries in the table.
     */
    private int size = 0;

    /**
     * The next size value at which to resize.
     */
    private int threshold; // Default to 0

    /**
     * Set the resize threshold to maintain at worst a 2/3 load factor.
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    /**
     * Increment i modulo len.
     */
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    /**
     * Decrement i modulo len.
     */
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    /**
     * Get the entry associated with key.  This method
     * itself handles only the fast path: a direct hit of existing
     * key. It otherwise relays to getEntryAfterMiss.  This is
     * designed to maximize performance for direct hits, in part
     * by making this method readily inlinable.
     *
     * @param  key the thread local object
     * @return the entry associated with key, or null if no such
     */
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

    /**
     * Set the value associated with key.
     *
     * @param key the thread local object
     * @param value the value to be set
     */
    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;
            }
        }

        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

    /**
     * Remove the entry for key.
     */
    private void remove(ThreadLocal<?> key) {
        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)]) {
            if (e.get() == key) {
                e.clear();
                expungeStaleEntry(i);
                return;
            }
        }
    }




    /**
     * Double the capacity of the table.
     */
    private void resize() {
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        int newLen = oldLen * 2;
        Entry[] newTab = new Entry[newLen];
        int count = 0;

        for (int j = 0; j < oldLen; ++j) {
            Entry e = oldTab[j];
            if (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null; // Help the GC
                } else {
                    int h = k.threadLocalHashCode & (newLen - 1);
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    newTab[h] = e;
                    count++;
                }
            }
        }

        setThreshold(newLen);
        size = count;
        table = newTab;
    }


}

 

小结1

<1> ThreadLocalMap类ThreadLocal内部静态类

<2> ThreadLocalMap默认大小是16。和HashMap差不多也存在扩容resize()方法。且每次扩容也是两倍。

<3> ThreadLocalMap类中有三个常用的方法

设置值

private void set(ThreadLocal<?> key, Object value)

@param key the thread local object 即 Key就是ThreadLocal类本身 使用的时候传入this。

@param value the value to be set   即 Value就是要设置的Object值。

获取值

private Entry getEntry(ThreadLocal<?> key)

key the thread local object 即 Key就是ThreadLocal类本身 使用的时候传入this。

清空值

private void remove(ThreadLocal<?> key) 即 Key就是ThreadLocal类本身 使用的时候传入this。

<4> ThreadLocalMap类中有一个内部静态类Entry(也就是ThreadLocalMap类中存储的Key-Value实体)是弱引用的。

static class Entry extends WeakReference<ThreadLocal<?>> {

}

 

继续

因为创建了t.threadLocals对象,所以下一次set的时候就直接 set值就可以了。

ThreadLocalMap map.set(this, value);

至此,设置值源码就讲解完了。下面看获取值。

 

 

获取值源码

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();
}

首先,获取当前线程,然后根据当前线程 获取ThreadLocalMap对象。

如果map不为空。继续获取map的Entry对象。获取Entry对象时Key为this。即ThreadLocal本身。最后通过Entry实体类获取Entry实体类的value。

如果map为空,则执行setInitialValue方法,初始化ThreaLocalMap。

 

setInitialValue方法源码

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

至此,获取值源码就讲解完了。下面看清空值。

 

 

清空值源码

remove()方法源码

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

首先,获取当前线程,然后根据当前线程 获取ThreadLocalMap对象。

然后执行map的remove()方法。传参this。即ThreadLocal本身。

 

 

总结

<1> ThreadLocal是存储 当前线程 变量的类。

<2> 内部有一个静态类ThreadLocalMap。ThreadLocalMap和HashMap类似。ThreadLocalMap类内部还有一个静态类Entry。Entry类是弱引用的主要考虑因为你的Key其实是ThreadLocal本身,然后又和当前线程有关。可能会造成内存泄漏

<3> ThreadLocal类本身提供三个方法。获取值+设置值+清空值。这三个方法都会首先获取当前线程,然后获取ThreadLocalMap对象。最后都是操作的ThreadLocalMap类的内部静态类Entry类。

<4> ThreadLocal类 是在一个线程中提供当前线程所有方法共享数据的。所以一般是静态的。比如Handler源码中操作Looper变量。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

<5> 使用ThreadLocal时,记住最好是在不用的地方清空一下。因为可能造成内存泄漏,且sThreadLocal 一般是静态的也需要置空。

 

 

 

 

 

 

 

三.ThreadLocal内存泄漏

上述源码已经分析过,无论是set还是get值。其实都是操作的ThreadLocalMap类的内部静态类Entry类。因为Entry类是弱引用的。

所以 ThreadLocalMap类其内部静态类Entry类。之间是弱引用的关系

但是 ThreadLocalMap类 ThreadLocal类 之间是强引用的。因为。

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

所以,如果当前线程在执行一个任务后,就销毁了的话是没有问题的。但是如果是线程池中的操作的话。可能就会有内存泄漏的问题。

 

所以,我们在使用ThreadLocal时,最后在适当的时机,调用ThreadLocal的remove()方法。

 

Entry中对ThreadLocal使用弱引用,但value是强引用。其实value作为强引用设计合理,如果使用软或弱引用,就出大问题了,程序跑着跑着,突然get到了一个null。就不好了。

 

 

 

 

 

 

四.ThreadLocal使用举例、

 

1.代码

public class ThreadLocalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threadlocal);

        ThreadLocal<String> threadLocal = new ThreadLocal<>();

        new Thread(() -> {
            threadLocal.set("线程A的变量 001");
            Log.d("ThreadLocalActivity", "线程A取值:" + threadLocal.get());
        }).start();

        new Thread(() -> {
            threadLocal.set("线程B的变量 002");
            Log.d("ThreadLocalActivity", "线程B取值:" + threadLocal.get());
        }).start();
    }
}

 

 

2.结果

D/ThreadLocalActivity: 线程A取值:线程A的变量 001



D/ThreadLocalActivity: 线程B取值:线程B的变量 002

 

 

3.总结

因为ThreadLocal内部使用的是ThreadLocalMap。而通过源码分析,这个Map的Key就是线程本身。所以在不同的线程set值。取值时不会错乱。即线程的隔离。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值