ThreadLocal

简介: ThreadLocal对象是线程的局部变量,每个线程都能在其中保存只属于自己的内容。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

ThreadLocal对象是线程的局部变量,每个线程都能在其中保存只属于自己的内容。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

结构体系大概如下:
image

ThreadLocal如何保证这些变量只被当前线程所访问的呢?

ThreadLocal.set

首先看一下ThreadLocal的set():

public class ThreadLocal<T> {

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    /**
     * 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();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
}

set时,首先获取当前线程对象,然后通过getMap获取线程的ThreadLocalMap,并将值设置ThreadLocalMap中,ThreadLocalMap可以理解为一个Map。map.set()如下:

    static class ThreadLocalMap {
        /**
         * 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();
        }
    }

方法中循环了map中的元素,对map中的key和入参key比较,如果相同则赋新值,如果map中key存在null值则替换,否则重新生成一个map元素。

如果没有map,则执行createMap():

    /**
     * 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);
    }

createMap中构造了ThreadLocalMap:

        /**
         * 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);
        }

在这里,为Thread的threadLocals赋值。

ThreadLocal.get

而设置到ThreadLocal中的数据,实质上是写入了Thread的threadLocals属性。其中key为ThreadLocal当前对象,value就是我们需要的值。Thread的threadLocals保存了当前自己所在线程的所有“局部变量”,也就是一个ThreadLocal变量的集合。

在进行get操作时,自然就是将这个Map中的数据拿出来:

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

首先,get方法也是先取得当前线程的ThreadLocalMap对象,然后,将自己传入作为getMap的参数,而getMap返回的则是当前线程的threadLocals属性:

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

即当前线程的threadLocals属性,也就是说,ThreadLocal类的getMap方法返回的是Thread类的threadLocals属性:

public class Thread implements Runnable {

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

因此,threadLocals是维护在Thread内部的,这意味着只要线程不退出,对象的引用就一直存在。

当线程退出时会进行一些清理工作,其中包括对ThreadLocalMap的清理:

    /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

如果使用线程池的话,线程未必会退出,此时将一些比较大的对象放入ThreadLocal中(实际保存在线程持有的threadLocals Map中),可能会导致内存泄漏的问题。这时可以使用ThreadLocal.remove()进行移除:

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

实例演示

public class Test {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static ThreadLocal<String> anotherThreadLocal = new ThreadLocal<>();
    static AtomicInteger atomicInteger = new AtomicInteger(0);

    static class ThreadDemo implements Runnable {

        @Override
        public void run() {
            if (threadLocal.get() == null) {
                threadLocal.set("线程:" + atomicInteger.addAndGet(1));
            }
            if (anotherThreadLocal.get() == null) {
                anotherThreadLocal.set("anotherThreadLocal:" + atomicInteger.get());
            }
            System.out.println(threadLocal.get());
            System.out.println(anotherThreadLocal.get());
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ThreadDemo());
        }
    }
}

表明,一个ThreadLocal可以供多个线程共享,但是每个线程只能拿到属于自己的那一份存储。每一个线程的threadLocals的大小可以为多个,每一个ThreadLocal对象都可以会作为该map的key存在。

目录
相关文章
|
3月前
|
Java 测试技术 索引
ThreadLocal详解
文章详细讨论了Java中的`ThreadLocal`,包括它的基本使用、定义、内部数据结构`ThreadLocalMap`、主要方法(set、get、remove)的源码解析,以及内存泄漏问题和避免策略。`ThreadLocal`提供了线程局部变量,确保多线程环境下各线程变量的独立性,但不当使用可能导致内存泄漏,因此建议在不再需要`ThreadLocal`变量时调用其`remove`方法。
105 2
ThreadLocal详解
|
6月前
|
存储 Java 数据管理
ThreadLocal的使用
`ThreadLocal`是Java中的线程局部变量工具,确保每个线程都有自己的变量副本,互不干扰。适用于保持线程安全性数据和跨方法共享数据。基本用法包括创建实例、设置和获取值以及清除值。例如,创建ThreadLocal对象后,使用`.set()`设置值,`.get()`获取值,`.remove()`清除值。注意ThreadLocal可能引起内存泄漏,应适时清理,并谨慎使用以避免影响代码可读性和线程安全性。它是多线程编程中实现线程局部数据管理的有效手段。
86 10
|
6月前
|
存储 Java
ThreadLocal 有什么用
ThreadLocal 有什么用
52 0
|
存储 Java
|
存储 分布式计算 安全
什么是ThreadLocal?
这篇文章是慕课网上一门免费课程《ThreadLocal》的观后总结。这门课将ThreadLocal讲得非常清晰易懂,又深入底层原理和设计思想,是我看过的最好的ThreadLocal的资料,现在把用自己的话,把它整理成文字版本。 总共预计产出四篇文章,这是第一篇。
266 3
|
存储 Java
对threadlocal了解多少?
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问题。 ThreadLocal 类主要解决的就是让每个线程绑定自己的值,可以将 ThreadLocal 类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
|
Java 应用服务中间件
我把 ThreadLocal 能问的,都写了(下)
我把 ThreadLocal 能问的,都写了(下)
我把 ThreadLocal 能问的,都写了(下)
|
存储 缓存 算法
再聊聊ThreadLocal
只要线程处于活动状态并且 ThreadLocal 实例可访问,那么每个线程都持有对其线程局部变量(thread-local)副本的隐式引用。在线程消失后,它的所有线程局部变量(thread-local)副本都将被垃圾回收(除非存在对这些副本的其他引用)
再聊聊ThreadLocal