ThreadLocal和InheritableThreadLocal

526 阅读5分钟

ThreadLocal

前言:

在Thread 线程类里面有一个字段:

ThreadLocal.ThreadLocalMap threadLocals = null;

这个字段属于当前线程的,在线程切换的过程中,该参数是不会被传递的,其实ThreadLocal 也就是操作这个参数,也就起到了一个线程隔离的作用 。从本质上来讲的话ThreadLocal.ThreadLOcalMap 就可以理解为Thread 线程对象里面的一个私有字段,并不会被子类或者其他对象访问的到

意义:

这是在线程锁 以外的可以保证这个变量,可以不被引用,或者说互相隔离

源码解析

get

/**
*返回此线程局部变量的当前线程副本中的值。如果变量没有当前线程的值,则首先 *将其初始化为调用initialValue方法返回的值。
*回报:
*此线程本地的当前线程的值
**/
public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的Map
    ThreadLocalMap map = getMap(t);
    //判断当前是否有map
    if (map != null) {
       //如果当前mao存在 通过this(当前线程作为key)获取value值 
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //如果Entry 不等于null(如果没设置就为null)
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果走到了这里,就代表调用get之前没有调用set方法
    return setInitialValue();
}

set

/**
*将此线程局部变量的当前线程副本设置为指定值。大多数子类不需要重写此方法, *仅依靠initialValue方法来设置线程局部变量的值。
*参数:
*value – 要存储在此线程本地的当前线程副本中的值。
**/
public void set(T value) {
   //获取当前线程
    Thread t = Thread.currentThread();
   //获取当前线程map
    ThreadLocalMap map = getMap(t);
   //判断是否存在,如果存在则直接设置
    if (map != null) {
        map.set(this, value);
    } else {
      //如果不存在初始化map
        createMap(t, value);
    }
}

createMap

void createMap(Thread t, T firstValue) {
   //这里其实就是创建了一个ThreadLocalMap的对象,或者说初始化 这样子
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

getMap

//这里的方法直接指向Thread 的ThreadLocal.ThreadLocalMap threadLocals = null; 这个字段上的

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
private T setInitialValue() {
   //这个参数默认返回是null 如果子类复写了他 那么就代替
    T value = initialValue();
    Thread t = Thread.currentThread();
   //获取threadLocals的值
    ThreadLocalMap map = getMap(t);
    if (map != null) {
       //初始化过: 将value值设置进去
        map.set(this, value);
    } else {
        //如果没有那么就创建一个 
        createMap(t, value);
    }
  //如果这个线程是此对象的实例,那么就将其注入到对应的Collection里面
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
  //返回默认值 如果没设置就是null
    return value;
}

remove

如果在调用该方法,且当前之后调用get方法可能会导致多次调用初始化的方法

public void remove() {
//获取ThreadLocal的map参数 
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
       //移除
        m.remove(this);
    }
}

注意:内存泄漏问题

我们知道Thread 线程里面有一个maplocal 那么这个参数是在ThreadLocal里面的一个内部类 实际上是一个Entry数组,但是这个数组是弱应用类型的一个数组。我们知道若引用类型,当发生GC的时候,该对象只有一个弱引用的时候,该对象会被GC回收掉,同时也存在一个问题 如果此时这个ThreadLocal的map对象被其他强引用那么就不会被回收掉,造成了内存泄漏 。

这里在补一段代码,Entry 这个类继承了弱引用的类,构造方法直接使用父类的,所以这是一个弱引用的类;

static class Entry extends WeakReference<ThreadLocal<?>> {
    //这个类其实就是-内存泄漏的根源
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

InheritableThreadLocal

前言:

通过查看ThreadLocal 我们可以隔离线程之间的一个数据交互方法,如果我们想让其线程之间有如此的数据交互怎么办,这是InheritableThread 这个类。

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 这个类呢和ThreadLocal本质上是一样的参数,通过不同的控制手段,来实现一个可以为子类访问,一个不能

源码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    //这个方法同包可用
    protected T childValue(T parentValue) {
        return parentValue;
    }
​
    //继承于父类方法-区别就是指向对象有区别
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
  /------------------ 父类方法实现
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
  /------------------  
​
    //也是继承于父类,区别和getMap方法一样
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

setInitialValue

初始化Value-次方法内部有着getMap的方法,ThreadLocal 有着自己的实现,也就是指向自己的Map,而子类,InheritableThreadLocal 指向的是inheritableThreadLocal 可继承的线程本地变量,通过查看父类的方法,我们可以看到,在初始化和设置的时候,都有调用这个方法,那么此时这个初始化指向就不同了,或者说初始化的对象就不一样了。

private T setInitialValue() {
   //调用初始化方法
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程ThreadLocal的map参数
    ThreadLocalMap map = getMap(t);
    //按照情况进行初始化
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
}

init

这个方法是一个初始化的方法,那么可以发现,在创建这个线程的时候,有对这个字段进行初始化inheritThreadLocals 这个字段,有一个是布尔值,意思就是是否继承父类的可继承变量,还有一个就是父类里面的一个map存储数据的字段,且只有当选择可继承为true 和 父类可继承变量为true的时候,才会进行初始化的,此时我们在使用inheritableThreadLocal 对象getMap的方法的时候,会直接指向inheritable 所以此时我们就可以访问的到父类的变量了。

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
​
    this.name = name;
​
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
 
        if (security != null) {
            g = security.getThreadGroup();
        }
​
  
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
​

    g.checkAccess();
​
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
​
    g.addUnstarted();
​
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    //这里是为子类提供字段访问的关键步骤
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
​
    /* Set thread ID */
    tid = nextThreadID();
}

set

这个方法是重新贴出来的,从这个set方法来说,我们可以得到一个结论,子类只是可以继承这个变量,但是并不可以,说继续影响到父类的这个变量。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}