threadlocal的使用以及源码解读

44 阅读4分钟

目录

1、thraedlocal是什么
2、如何使用threadlocal
3、实际应用代码编写
4、threadlocal源码分析
5、threadlocal内存泄漏问题

threadlocal是什么

ThreadLocal是jdk中的一个类,在多线程并发中,为每个线程提供一个内部的局部变量。实现在同一线程内不同组件间的数据传递,且多个线程不相互影响即线程安全。

定义中的核心 1、多线程并发 2、数据传递 3、线程安全

threadlocal为我们提供了三个核心的方法

方法名说明
public void set( T value)设置当前线程绑定的局部变量
public T get()获取当前线程绑定的局部变量
public void remove()移除当前线程绑定的局部变量

如何使用threadlocal

简单感受下threadlocal的应用

public class ThreadLocalDemo {

    /**
     * 全局的一个变量
     */
    private static ThreadLocal<String> tl = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {

        tl.set("main thread");
        Thread thread = new Thread(()->{
            tl.set("sub thread");
            System.out.println(Thread.currentThread().getName()+":::::" + tl.get());
        });

        thread.start();
        thread.join();

        System.out.println(Thread.currentThread().getName() + ":::::" + tl.get());
    }
}

threadlocal使用场景分析

我们在实际开发中会分层,比如controller-service-dao层,如果说dao层需要调用controller层中的一个对象,那么该怎么做呢,常见的想法,在每个方法上的参数里面,带上那个对象,如上图所示。这样耦合极大。

那我们是否有更好的解决方法?下面就介绍下threadlocal这个对象。

//见代码

threadlocal源码分析

在进行源码分析前,我们先了解下threadlocal的结构

set方法

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

ThreadLocal之set流程: 1、获取当前线程t; 2、返回当前线程t的成员变量ThreadLocalMap(以下简写map); 3、map不为null,则更新以当前线程为key的ThreadLocalMap,否则创建一个ThreadLocalMap,其中当前线程t为key;

get方法

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

ThreadLocal之get流程: 1、获取当前线程t; 2、返回当前线程t的成员变量ThreadLocalMap(以下简写map); 3、map不为null,则获取以当前线程为key的ThreadLocalMap的Entry(以下简写e),如果e不为null,则直接返回该Entry的value; 4、如果map为null或者e为null,返回setInitialValue()的值。setInitialValue()调用重写的initialValue()返回新值(如果没有重写initialValue将返回默认值null),并将新值存入当前线程的ThreadLocalMap(如果当前线程没有ThreadLocalMap,会先创建一个)。

threadlocal内存泄露问题

每个thread中都存在一个map,map的类型是ThreadLocal.ThreadLocalMap,Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每个key都弱引用指向threadlocal。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用。只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread,Map,value将全部被GC回收。 所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。 在使用完成后注意调用remove方法清理。养成良好习惯