Java ThreadLocal-2

86 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

3.1 线程隔离

在2.4ThreadLocal的使用示例中,我们发现一个很有趣的事情,ThreadLocal在不同的线程,好像能够存储不同的数据:就好像ThreadLocal本身就有存储功能,到了不同线程,能够生成不同的“副本”存储数据一样。

实际上,ThreadLocal到底是怎么做到的呢?

看下set方法,看看到底怎么存数据的,这就用到2.3章节提到的ThreadLocalMap。

ThreadLocal类

 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value){
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if(map != null){
        map.set(this,value);
    }else {
        createMap(t,value);
    }
}
 
//获取当前Thread的threadLocals变量
/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocal.ThreadLocalMap getMap(Thread t){
    return t.threadLocals;
}

其实这地方做了一个很有意思的操作:线程数据隔离的操作,是Thread类和ThreadLocal类相互配合做到的

public class Thread implements Runnable{
    ...
    /*ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class.
    **/
    ThreadLocal.ThreadLocalMap threadLocals = null;
     
    ...
}

在上边的代码中可以看出来,在set数据的时候,会获取这行该操作的当前线程

Thread t = Thread.currentThread();

拿到当前线程,取到threadLocals变量,然后以当前实例为key,数据为value存入ThreadLocalMap中。

所以使用ThreadLocal在不同的线程中进行写操作,实际上数据都是绑定在当前线程的实例上,ThreadLocal只负责读写操作,并不负责保存数据,这就解释了,为什么ThreadLocal set数据,只在操作的线程中用。

大家有没有感觉这种思路有些巧妙!

3.2 ThreadLocalMap

另外我们可以从源码中看到每一个Thread维护一个ThreadLocalMap,ThreadLocalMap中key为使用弱引用的ThreadLocal实例,value为线程变量的副本。一定程度上减少了内存泄露的风险,但如果使用不当也会引起内存泄露。

 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
*/
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;
        }
    }
    ...
}

3.3与Synchronized比较

使用领域不同:

Synchronized是用于多线程下相同资源并发访问的一种有效方式;

ThreadLocal是隔离多个线程的数据共享。

使用ThreadLocal存储数据其实是将数据存在了线程的私有内存里面,就不会存在线程安全问题、多线程下资源访问问题。

四 注意事项

1.使用完ThreadLocal变量后,确定不再需要用到该变量,调用其remove方法,避免内存泄漏;

2.一个ThreadLocal实例,在一个线程中只能储存一类数据,后期的set操作,会覆盖之前的set的数据;

3.ThreadLocal变量通常被定义为private static 。

{@code 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).

这样做的目的是希望与当前线程的状态相关联,一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

五 总结

ThreadLocal对象本身是不储存数据的,它本身的职能是执行相关的set、get之类的操作,如果需要隔离多个线程之间的共享冲突,可以考虑使用ThreadLocal,能简化我们的程序。