ThreadLocal解析

23 篇文章 0 订阅

 

一、ThreadLocal简介

ThreadLocal是JDK包提供的工具类,主要用于将线程和该线程存放的副本对象做一个映射。在实际多线程操作的时候,操作的是自己本地内存中的变量,各个线程之间的变量互不干扰,从而规避了线程安全问题。如下图所示:

ThreadLocal是除了加锁这种同步方式之外,另一种保证在多线程访问时,避免出现线程安全问题的方法,特别适用于各个线程依赖不同的变量值完成操作的场景。

二、ThreadLocal实现原理

1. ThreadLocal类

  • get方法用于获取当前线程的副本变量值
/**
 * 1.获取当前线程的ThreadLocalMap对象threadLocals
 * 2.从map中获取线程存储的K-V Entry节点
 * 3.从Entry节点获取存储的Value副本值返回
 * 4.map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判NullPointerException
 */
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();
}

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

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;
}
  • set方法用于保存当前线程的副本变量值
/**
 * 1.获取当前线程的成员变量map
 * 2.map非空,则重新将ThreadLocal和新的value副本放入到map中
 * 3.map空,则对线程的成员变量ThreadLocalMap进行初始化创建,并将ThreadLocal和value副本放入map中
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
  • initialValue为当前线程初始副本变量值
protected T initialValue() {
    return null;
}
  • remove方法移除当前线程的副本变量值
/**
 * 1.获取当前线程的成员变量m
 * 2.m非空,则直接删除当前线程中指定的threadLocals变量
 */
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

2. ThreadLocalMap内部类


        从上文的类图可知,ThreadLocalMap是ThreadLocal的静态内部类。在Thread类中,有两个ThreadLocalMap类型的成员变量threadLocals和inheritableThreadLocals,只有在线程第一次调用ThreadLocal的set或者get方法时,才会对它们进行创建、赋值。

ThreadLocalMap的结构与WeakHashMap非常相似,但是它并没有去实现Map接口,而是通过自定义Entry[]的方式,实现了一套K-V结构保存数据的功能。

3. Entry内部类

/**
 * 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<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

Entry类继承自WeakReference,是以ThreadLocal为key,Object为value的K-V数据结构。由于key是弱引用类型,所以在ThreadLocal对象的引用被置成null后,key就会自动在下一次GC时被垃圾回收。

4. InheritableThreadLocal类

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue()、getMap()、createMap()三个方法。其中,createMap()方法在被调用的时候,创建的是inheritableThreadLocal,而不是threadLocals。同理,getMap()方法在被调用的时候,创建的也不是threadLocals而是inheritableThreadLocal。

new Thread()

init(ThreadGroup g, Runnable target, String name, long stackSize)
                       

init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals)

-> 
           if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

createInheritedMap(ThreadLocalMap parentMap)


ThreadLocalMap(ThreadLocalMap parentMap) 

跟踪new Thread的调用路径可知:在init方法中,判断了当前父线程的inheritableThreadLocals是否为null,然后调用createInheritedMap将父线程的inheritableThreadLocals作为构造函数参数创建了一个新的ThreadLocalMap变量,然后赋值给子线程。

三、ThreadLocal常见问题

1. 脏数据

由于Java线程池会复用Thread对象,所以与Thread绑定的静态ThreadLocal变量也会被复用。如果在Thread的run()方法里,没有调用remove() 方法做清理,且复用Thread对象不调用set() 方法设置初始值,则调用get() 方法可能获取到复用的线程信息。

2. 内存泄漏

Entry对象的key通过弱引用指向ThreadLocal对象的,但是ThreadLocal对象不持有value,而是由线程的Entry对象持有的强引用。当使用static关键字来修饰ThreadLocal(源码注释中推荐),发生GC时,弱引用的key会被回收,而强引用的value不会被回收,发生内存泄露。所以在每次用完ThreadLocal后, 需要及时调用remove()方法做清理。

四、ThreadLocal使用建议

1. 声明为全局static final成员

2. 避免存储大量对象

3. 用完后及时移除对象

4. 通过InheritableThreadLocal类访问父线程的本地变量(线程池不支持)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值