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