likes
comments
collection
share

ThreadLocal 到底是啥

作者站长头像
站长
· 阅读数 54

ThreadLocal 到底是啥,我们基于java8来看看,它到底是个什么东西

ThreadLocal

以下为测试代码

ThreadLocal 到底是啥

测试代码中可以看到,threadlocal 初始化在最外层,各个线程数内操作设值跟获取都互不影响,即使是对同一个threadlocal进行操作。

源码

public class ThreadLocal<T> {  
  
    public void set(T value) {  
        // 获取当前线程  
        Thread t = Thread.currentThread();  
        //获取当前线程的map  
        ThreadLocalMap map = getMap(t);  
        if (map != null) {  
            //map对应key为对应调用的threadLocal实例对象,value为本次设置的值  
            map.set(this, value);  
        } else {  
            createMap(t, value);  
        }  
    }  

	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) {  
	    //Thread中的属性ThreadLocal.ThreadLocalMap threadLocals = null;
        return t.threadLocals;  
    }  
	  
	void createMap(Thread t, T firstValue) {  
	    t.threadLocals = new ThreadLocalMap(this, firstValue);
	}

	ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {  
	    table = new Entry[INITIAL_CAPACITY];  
	    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  
	    table[i] = new Entry(firstKey, firstValue);  
	    size = 1;  
	    setThreshold(INITIAL_CAPACITY);  
	}

}  

ThreadLocal的get操作还是set操作,第一步都是先获取当前线程,设值到线程的ThreadLocal.ThreadLocalMap中、获取值也是从线程的ThreadLocal.ThreadLocalMap中获取。

Thread 没有任何保存当前数据的功能,但有对应的属性。我们想要保存一些当前线程才有的数据,并且线程件无法互用,于是有了ThreadLocal 的存在。Thread 只提供了一个属性ThreadLocal.ThreadLocalMap threadLocals来给 ThreadLocal 进行操作。

ThreadLocal 到底是啥

ThreadLocalMap

我们来看下ThreadLocalMap到底是什么


/**  
 *  ThreadLocal.ThreadLocalMap的Entry继承了WeakReference,是弱引用  
 **/  
public class ThreadLocal<T> {

	private final int threadLocalHashCode = nextHashCode();

	private static AtomicInteger nextHashCode = new AtomicInteger();

	//同一个线程,每创建一次都会对 threadLocalHashCode 进行获取新值
	private static int nextHashCode() {  
	    return nextHashCode.getAndAdd(1640531527);  
	}

	static class ThreadLocalMap {

		private Entry[] table;
		private static final int INITIAL_CAPACITY = 16;

		ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {  
		    table = new Entry[INITIAL_CAPACITY];  
		    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  
		    table[i] = new Entry(firstKey, firstValue);  
		    size = 1;  
		    setThreshold(INITIAL_CAPACITY);  
		}

		static class Entry extends WeakReference<ThreadLocal<?>> {  
			
		    Object value;  
		  
		    Entry(ThreadLocal<?> k, Object v) {  
		        super(k);  
		        value = v;  
		    }  
		}
		private void set(ThreadLocal<?> key, Object value) {   
		    Entry[] tab = table;  
		    int len = tab.length;  
		    int i = key.threadLocalHashCode & (len-1);  

			//设值到tab中,如果key一致,则更换value
			//否则则新增
		    for (Entry e = tab[i];  
		         e != null;  
		         e = tab[i = nextIndex(i, len)]) {  
		        ThreadLocal<?> k = e.get();  
		  
		        if (k == key) {  
		            e.value = value;  
		            return;  
		        }  
		  
		        if (k == null) {  
		            replaceStaleEntry(key, value, i);  
		            return;  
		        }  
		    }  
		  
		    tab[i] = new Entry(key, value);  
		    int sz = ++size;  
		    if (!cleanSomeSlots(i, sz) && sz >= threshold)  
		        rehash();  
			}

		private static int nextIndex(int i, int len) {  
		    return ((i + 1 < len) ? i + 1 : 0);  
		}
	}
}

对于 ThreadLocalMap来说,就是将数据放置到Entry数组里,key为当前线程所set的thraedlocal对象

ThreadLocal.ThreadLocalMap.Entry 中的key是弱引用的,也就是当某个ThreadLocal对象不存在强引用时,就会被GC回收,这样设计是因为当当前线程还存活,但是已经不再执行引用,ThreadLocal.ThreadLocalMap.Entry中的key如果是强引用会一直不被回收,我们的线程也存活着,就会导致内存泄漏,所以key被设计成弱引用。

但是value是基于强引用的,所以当key被回收,但是value还存在其他强引用时,就会出现内存的泄露情况,所以要养成用完 ThreadLocal 对象之后及时 remove 的习惯,remove会将对应的 ThreadLocal 对象所在的entry直接清除引用。

value使用强引用的可能是,如果value为弱引用,这时候内存不够,value被删了,key还在,这时候会出现,对于entry应该成对存在的值,key存在,value却没了,可能会导致其他bug的产生。

内存泄露 (Memory Leak):就是申请了内存,但是没有释放,导致内存空间浪费,也就是占着茅坑不拉屎

ThreadLocal<String> threadLocal = new ThreadLocal<>();  
    try {  
    threadLocal.get();  
} catch (Exception e) {  
    threadLocal.remove();  
}

InheritableThreadLocal

子类线程获取主线程的信息,使用InheritableThreadLocal,Inheritable就是可以继承的意思

对应测试用例

public static void main(String[] args) {  
    InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();  
    ThreadLocal<String> threadLocal = new ThreadLocal<>();  
    inheritableThreadLocal.set("Inheritable hello");  
    threadLocal.set("hello");  
    new Thread(() -> {  
        System.out.println(String.format("子线程可继承值:%s", inheritableThreadLocal.get()));  
        System.out.println(String.format("子线程值:%s", threadLocal.get()));  
        new Thread(() -> {  
            System.out.println(String.format("孙子线程可继承值:%s", inheritableThreadLocal.get()));  
            System.out.println(String.format("孙子线程值:%s", threadLocal.get()));  
        }).start();  
  
    }).start();  
}

结果

ThreadLocal 到底是啥 InheritableThreadLocal 的源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {  
  
    protected T childValue(T parentValue) {  
        return parentValue;  
    }  
  
    ThreadLocalMap getMap(Thread t) {  
        return t.inheritableThreadLocals;  
    }  
  
    void createMap(Thread t, T firstValue) {  
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);  
    }  
}

通过debug,查看是如何实现获取父线程

当子线程调用inheritableThreadLocal.get()时,可以清楚看到,获取map时,是执行InheritableThreadLocal所重写的getMap,返回当前线程的inheritableThreadLocals 属性。 跟threadLocal 一样,Thread也是有一个对应属性inheritableThreadLocals 来保存对应的父线程信息。

问题来了,当前线程的inheritableThreadLocals 属性是什么时候设值的,从我们的测试代码可以看出,我们就是创建了子线程,然后就执行inheritableThreadLocal.get(),我们可以看下创建线程的方法有什么东西


public class Thread implements Runnable {


	ThreadLocalMap inheritableThreadLocals = null;

	//创建线程的方法  
	public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);}  
	  
	private 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();  
	   ...  
	    //查看父线程有没有inheritableThreadLocals,  
	    if (inheritThreadLocals && parent.inheritableThreadLocals != null)  
	        //有就保存到新建线程的inheritableThreadLocals,这样来实现,子线程、孙线程获取到父线程的可继承信息数据  
	        this.inheritableThreadLocals =  
			                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);  
	   ...  
		}
}

总的来说

InheritableThreadLocal 跟threadLocal一样,起到的只有设值的作用,对应的属性保存到在Thread里。

转载自:https://juejin.cn/post/7234763992332976183
评论
请登录