JAVA多线程(十)Java多线程之ThreadLocal

冷暖自知 提交于 2020-01-19 03:53:05

1.JAVA多线程(十)Java多线程之ThreadLocal

1.1 ThreadLocal类

   ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get()和 set())方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的,由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!

1.2 ThreadLocal示例

package com.yuanxw.chapter10;
import java.util.Random;

/**
 * ThreadLocal
 * 线程局部变量
 */
public class ThreadLocalExample {
    private static ThreadLocal<String> defaultThreadLocal = new ThreadLocal(){
        @Override
        protected Object initialValue() {
            return "==initialValue==";
        }
    };

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

    public static void main(String[] args) throws InterruptedException {
        System.out.println("获得defaultThreadLocal默认值:"+defaultThreadLocal.get());

        // 线程-A
        Thread thread1 = new Thread(() -> {
            // 设置【threadLocal】对象值为:张三
            threadLocal.set("张三");
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("【%s】线程-执行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
        }, "Thread-A");

        // 线程-B
        Thread thread2 = new Thread(() -> {
            // 设置【threadLocal】对象值为:李四
            threadLocal.set("李四");
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("【%s】线程-执行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
        }, "Thread-B");

        thread1.join();
        thread2.join();

        thread1.start();
        thread2.start();

        Thread.sleep(new Random().nextInt(1000));
        System.out.println(String.format("【%s】线程-执行threadLocal值:【%s】", Thread.currentThread().getName(),threadLocal.get()));
    }
}

执行结果:

获得defaultThreadLocal默认值:==initialValue==
【Thread-B】线程-执行threadLocal值:【李四】
【Thread-A】线程-执行threadLocal值:【张三】
【main】线程-执行threadLocal值:【null】

1.3 ThreadLocal对应的底层结构图

每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象。当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal->value 键值对插入到该 Map 中,get() 方法类似。

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

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

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

1.3 ThreadLocal 内存泄露问题

在一些场景 (尤其是使用线程池) 下,ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法。

    – 以上为《JAVA多线程(十)Java多线程之ThreadLocal》,如有不当之处请指出,我后续逐步完善更正,大家共同提高。谢谢大家对我的关注。

——厚积薄发(yuanxw)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!