ThreadLocal总结分析

149 阅读3分钟

Threadlocal是什么?有什么用?

个人学习,原文链接,感谢原文作者www.cnblogs.com/fsmly/p/110…

多个线程对同一个共享变量进行访问的时候容易出现问题。
为了保证线程安全,可以使用加锁这种方式来保证,也可以通过使用Threadlocal

当创建一个变量后,每个线程使用的都是线程本地自己的变量
ThreadLocal是JDK包提供的,它提供线程本地变量,
如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,
操作的是自己本地内存中的变量,从而规避了线程安全问题

重点是:每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)

public class ThreadLocalTest {
    static ThreadLocal<String> strLocal = new ThreadLocal<>();  //threadlocal变量

    static void print(String str){
        System.out.println(str+" : " + strLocal.get());
        strLocal.remove();      //删除此线程局部变量的当前线程值
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                strLocal.set("local1");
                System.out.println("thread1: "+strLocal.get());
                print("thread1");
                System.out.println("after remove : " + strLocal.get());
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                strLocal.set("local2");
                System.out.println("thread2: "+strLocal.get());
                print("thread2");
                System.out.println("after remove : " + strLocal.get());
            }
        });
        //每个线程中的值互不影响
        t1.start();
        t2.start();
    }
}

输出以下内容。可以看到,每个线程中的值都是互不影响的

thread1: local1
thread1 : local1
thread2: local2
thread2 : local2
after remove : null
after remove : null

ThreadLocalMap


/ * The entries in this hash map extend WeakReference, using
 * its main ref field as the key (which is always a
 * ThreadLocal object).  Note that null keys (i.e. entry.get()
 * == null) mean that the key is no longer referenced, so the
 * entry can be expunged from table.  Such entries are referred to
 * as "stale entries" in the code that follows.
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** 与此ThreadLocal关联的值. */
    Object value;

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

每个Entry中包含一个Key-value,key为threadlocal,value为具体的对象。持有的是ThreadLocal的弱引用

ThreadLocal实现原理,首先看getMap方法

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

这个方法返回一个ThreadLocalMap类型的值,threadLocals是Thread类中的一个ThreadLocalMap变量,初始值为null

set、get、setInitialValue方法

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的Threadlocals变量
    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;
        }
    }
    //map为空的话设置初始值
    return 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;
}

public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的Threadlocals变量
    ThreadLocalMap map = getMap(t);
    //不为空设置值
    if (map != null)
        map.set(this, value);
     //为空创建
    else
        createMap(t, value);
}

每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。

image.png

ThreadLcoal的继承性

同一个变量在父线程设置后,子线程是获取不到的。,因为每个threadlcoal存放的是当前线程的值。父线程设置的值,在子线程是不能获取的,如下。

public class ThreadLocalTest2 {
    //创建一个threadlocal变量
    public static ThreadLocal<String> strLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //主线程添加变量
        strLocal.set("这个是主线程的变量");

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程中的变量值: " + strLocal.get());
            }
        });
        t1.start();

        System.out.println("主线程中的变量值: " + strLocal.get());

    }
}

//输出
//主线程中的变量值: 这个是主线程的变量
//子线程中的变量值: null

假设可以被子线程访问到,那么ThreadLcoal就无法保证线程安全了吗?应该就没有意义了。

InheritableThreadLocal

这个类可以支持子线程访问父线程的变量。 InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。 同样是上边的代码,将ThreadLocal类替换成InheritableThreadLocal类,可以看到输出是一样的。

关于内存泄漏

THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。