Thread、ThreadLocal、ThreadLocalMap学习笔记

1 先上一张经典的关系图,完了贴下原始的代码。

纯看代码写的真是挺绕的,先来张图有个整体的印象吧。

对着Thread类的源码如下:每个Thread类都会有两个这样的Map变量:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

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

这两个变量是专门存储Thread的局部变量用的。为啥整俩,有区别的,区别就再Thread的init方法里面,inheritableThreadLocals这个会继承父线程的数据信息。

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
      //上面的代码省略。。。。。 
        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 (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();
    }

ThreadLocal 大家写的时候基本的用法都张这样:

static final ThreadLocal<int[]> threadHashCode = new ThreadLocal<int[]>();

private static final ThreadLocal<char[]> DEST_TL =
      new ThreadLocal<char[]>() {
        @Override
        protected char[] initialValue() {
          return new char[1024];
        }
      };

 private final ThreadLocal<List<Object>> threadList;

ThreadLocal 的set方法张这样

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal的解释

ThreadLocal 就是用于线程(Thread)私有(Local)的存储结构,这种结构能够使得线程能够使用只有自己能够访问和修改的变量,从而实现多个线程之间的资源互相隔离,达到安全并发的目的。
通过上面的代码,可以理解为我们给Thread搞些局部变量的时候,其实是通过ThreadLocal去操作Thread.ThreadLocalMap的。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的静态内部类,是一个map结构,key是WeakReference<ThreadLocal<?>类型,value是object。注意这个key是WeakReference类型的。

WeakReference类型是什么意思呢?

搞段代码实验下:

//---------------------------------------------------------
public class Car {
  private double price;
  private String colour;

  public Car(double price, String colour){
    this.price = price;
    this.colour = colour;
  }

  public double getPrice() {
    return price;
  }
  public void setPrice(double price) {
    this.price = price;
  }
  public String getColour() {
    return colour;
  }
  public void setColour(String colour) {
    this.colour = colour;
  }

  public String toString(){
    return colour +"car costs $"+price;
  }

}

//---------------------------------------------------------
public class TestWeakReference {
  public static void main(String[] args) {

    Car car = new Car(22000,"silver");
    WeakReference<Car> weakCar = new WeakReference<Car>(car);

    int i=0;

    while(true){
      if(weakCar.get()!=null){
        i++;
        System.out.println("Object is alive for "+i+" loops - "+weakCar);
      }else{
        System.out.println("Object has been collected.");
        break;
      }
    }
  }
}

//-----------------------运行结果如下:
.......
Object is alive for 85254 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85255 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85256 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85257 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85258 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85259 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85260 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85261 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85262 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85263 loops - java.lang.ref.WeakReference@46fbb2c1
Object has been collected.

WeakReference对象的特性就是代码运行一段时间,如果碰到gc,WeakReference指向的对象会被gc回收。

WeakReference的一个特点是它何时被回收是不可确定的, 因为这是由GC运行的不确定性所确定的. 所以, 一般用weak reference引用的对象是有价值被cache, 而且很容易被重新被构建, 且很消耗内存的对象.
#另一种常用的引用类型介绍:SoftReference
soft reference和weak reference一样, 但被GC回收的时候需要多一个条件: 当系统内存不足时, soft reference指向的object才会被
回收. 正因为有这个特性, soft reference比weak reference更加适合做cache objects的reference. 因为它可以尽可能的retain       
cached objects, 减少重建他们所需的时间和消耗.

ThreadLocalMap的key为啥要设置成WeakReference类型呢?

就是为了防止内存泄漏,让gc回收,没别的。 value也设置成WeakReference岂不更好,感觉上毕竟value是从外面传入的,万一被别人引用了呢。纯猜的。

并且ThreadLocalMap源码里面有个这个方法expungeStaleEntry(),这个方法的目的就是将key为null的Entry的velue设置为null,在ThreadLocal的get(),set(),remove()的时候都会直接或者间接调用这个方法。相当于加了个保险,如果key被gc回收后,那你只要有操作map,我就给你做清楚操作。相当于为内存泄漏又加了个保险。

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

有个哥们儿总结的挺牛逼:

从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
我们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了帮助处理非常大且长期使用的用法,哈希表条目使用WeakReferences作为键。

下面我们分两种情况讨论:

key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

什么情况下内存会泄漏呢?

假设你用的是线程池,那池子里面的线程自始至终都是活的,线程不被销毁,并且你用完后就没怎么操作过这个ThreadLocal,key虽然会在gc时被回收,value一直被ThreadLocalMap引用着,可能会造成value的累积。

个人感觉

大家都说ThreadLocal用的时候会造成内存泄漏,原因呢大部分归结到弱引用,完了巴拉巴拉,说的人一头雾水。给我的感觉哈其实ThreadLocalMap用了WeakReferences做为键,并且在set,get,remove里面清除value,恰恰是未了防止内存泄漏。他写的时候想法很好,我把key设置WeakReferences,gc的时候就回收了,完了你再操作ThreadLocal的时候呢,我把value也帮着你删了。泄漏的原因其实就是你不会用。。。。。哈哈

解决方案

记得用完remove了 兄弟。

他们写的都挺牛逼,可以拜读下:

关于ThreadLocal内存泄露的备忘
深入分析 ThreadLocal 内存泄漏问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容