ThreadLocal 小结

来源:互联网 发布:手机怎么修改mac地址 编辑:程序博客网 时间:2024/06/08 10:46

(关于 ThreadLocal 的网上应该有很多优秀的文章,本文用于个人整理,以及有需要的人拿来参考,有什么不正确的地方,欢迎指正,共同进步。)

ThreadLocal 通常被称为本地线程变量,为什么呢?因为在多线程环境下,通过ThreadLocal,我们只要使用一个ThreadLocal对象,就可以为每个线程都可以维护一个该对象的副本,而且每个线程都可以单独修改自己的副本,并且不会影响到其他线程的副本,那么ThreadLocal是怎么实现这个强大的功能的呢?

我们来一起探索一下:

ThreadLocal主要的方法和内部类:

1、set 方法;

2、get 方法;

3、remove 方法

4、ThreadLocalMap:维护键值对(以 threadLocal 本身为键,set 方法中的参数值为 value),不太明白的话,请看后文。

我们来具体看看这几个方法:

1、set 方法;

ThreadLocal<String> threadLocal = new ThreadLocal<String>();threadLocal.set("ss");
代码很简单,第一行新建一个 threadLocal 对象,第二行将字符串“ss” set到 threadLocal 对象中,然后结果呢?结果就是 threadLocal 为当前线程维护了该对象的独立的副本,下面我们来看一下源代码

public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//通过当前线程找到相应的 mapThreadLocalMap map = getMap(t);if (map != null)//map如果不为空,也就是之前已经进行过set操作map.set(this, value);//以当前 threadLocal 对象为键,set 方法的入参为值放到 map中,并取代旧值else//map 为空,则新建一个 mapcreateMap(t, value);}
获取当前线程没有问题,下一行 getMap(t),是如何获取的呢?为什么通过 当前线程可以获取到 threadLocal 中的静态内部类 ThreadLocalMap对象呢?原来在 线程类Thread中将它作为 Thread 的一个属性了!这个是 ThreadLocal 实现本地线程变量的关键!!!通过这个getMap(Thread t)方法,对于每个线程都可以得到不一样的ThreadLocalMap。

public class Thread implements Runnable {/*     * InheritableThreadLocal values pertaining to this thread. This map is     * maintained by the InheritableThreadLocal class.       */     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

我们再来关注 map.set(this, value); 这一行,很明显,我们可以看到这个 ThreadLocalMap 是以当前的 threadLocal 对象作为key,以set的入参值作为 value进行设置,分析到这里了,我们就进到这个 map.set(this, value); 方法内部来仔细解析一下吧。

private void set(ThreadLocal key, Object value) {Entry[] tab = table;//获取当前 ThreadLocalMap 的 Entry 数组int len = tab.length;//获取 Entry 数组的长度int i = key.threadLocalHashCode & (len-1);//根据 key 的 hashcode 计算出 数组下标 与 HashMap 类似for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {//这里有点不解,为什么要循环 Entry 数组ThreadLocal k = e.get();if (k == key) {//覆盖旧值,并结束e.value = value;return;}if (k == null) {//为什么key会为null值呢?我们在remove方法介绍的时候将会叙述replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();//重新计算hash算法}

2、get 方法

threadLocal.get()
太简单了,这一句就可以获取到当前线程的threadLocal维护的副本,但是里面的实现是怎样的呢?我们来看一下源代码:

public T get() {//获取当前线程Thread t = Thread.currentThread();//通过当前线程找到相应的 mapThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null)return (T)e.value;}return setInitialValue();}
其实看懂了上面的 set方法,这个 get 方法也是蛮简单的了,获取当前线程——>获取当前线程的 ThreadLocalMap——>返回值或者进行初始化工作,我们就不展开叙述了。

3、remove 方法

这里我们必须强调的一点就是,当你使用完threadLocal对象时,必须要调用remove方法,不然后果蛮严重,为什么呢?我们来具体看一下源代码

public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             m.remove(this);     }
前面几行没有什么疑问,最后一行调用ThreadLocalMap 的remove(this)方法,将当前线程维护的副本清空,那为什么一定要remove呢?我们来看一下这个ThreadLocalMap静态内部类的结构

static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal> {//将 ThreadLocal 虚引用作为 key 当发生GC的时候会对key进行回收,但是不回收 valueObject value;Entry(ThreadLocal k, Object v) {super(k);value = v;}}...}

将 ThreadLocal 弱引用作为 key 当发生GC的时候会对key进行回收,但是不回收 value,也就是说就发生GC的时候,只是对key进行了垃圾回收处理,对value不管不问,然后一直在内存中保留着,导致了内存溢出,因此在上面的set方法中,会有 if (k == null)这个判断(可能发生了 key 回收,导致 key 为 null)以及后续处理。

为什么弱引用会在GC的时候被回收,其实引用分为以下几种,我们简单来介绍一下:

1、强引用:我们平时接触到的基本都是强引用,eg:Object object = new Object(); 或者String string = new String(); 中 object 和 string都是强引用,就算是虚拟机内存不够他们的空间也不会被回收,而是抛出 OutOfMemoryError 的错误;

2、软引用:可有可无,但是在没有内存的时候才会进行回收,否则一直保留在内存中;

3、弱引用:可有可无,但是比软引用拥有更加短的生命周期,在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会将它回收;

4、虚引用:形同虚设,虚引用主要用来跟踪对象被垃圾回收的活动。




1 0