• Threadlocal源码分析以及其中WeakReference作用分析


    今天在看Spring 3.x企业应用开发实战,第九章 Spring的事务管理,9.2.2节ThreadLocal的接口方法时,书上有提到Threadlocal的简单实现,我就去看了下JDK1.8的Threadlocal的源码。发现实现方式与书中讲的并不相同,同时在网上搜索了一下,发现有比较多的人理解错了。

    先看一下容易误导的解释:在ThreadLocal类中有一个Map对象,这个Map以每个Thread对象为键,保存了这个线程对应局部变量值,对应的实现方式如下:

    1. public class SimpleThreadLocal {
    2. private Map valueMap = Collections.synchronizedMap(new HashMap());
    3. public void set(Object newValue) {
    4. valueMap.put(Thread.currentThread(), newValue);//①键为线程对象,值为本线程的变量副本
    5. }
    6. public Object get() {
    7. Thread currentThread = Thread.currentThread();
    8. Object o = valueMap.get(currentThread);//②返回本线程对应的变量
    9. if (o == null && !valueMap.containsKey(currentThread)) {//③如果在Map中不存在,放到Map 中保存起来。
    10. o = initialValue();
    11. valueMap.put(currentThread, o);
    12. }
    13. return o;
    14. }
    15. public void remove() {
    16. valueMap.remove(Thread.currentThread());
    17. }
    18. public Object initialValue() {
    19. return null;
    20. }
    21. }

    为什么不按照那种有误的方法实现呢?
    看起来似乎是不同线程获取了各自的值,但是这些值并没有线程独立。线程A可以操作线程B对应的值。如果某个线程将保存这些值的Map置为null了,那么其他线程也无法访问了。

    实际上是怎样的呢
    我们看ThreadLocal的get()方法源码。可以看到获取Threadlocal中的值,是先通过当前线程的线程对象t,获取t的ThreadlocalMap属性对象,然后再以Threadlocal对象为键,去获取ThreadlocalMap中的值。

    1. public T get() {
    2. Thread t = Thread.currentThread();
    3. ThreadLocalMap map = getMap(t);//获取线程对象的属性
    4. if (map != null) {
    5. ThreadLocalMap.Entry e = map.getEntry(this);//这里的this是指Threadlocal对象,Threadlocal在类中通常以静态属性出现,所以多个线程的Threadlocal指向同一个对象。
    6. if (e != null) {
    7. @SuppressWarnings("unchecked")
    8. T result = (T)e.value;
    9. return result;
    10. }
    11. }
    12. return setInitialValue();
    13. }
    14. ThreadLocalMap getMap(Thread t) {
    15. return t.threadLocals;
    16. }

    同时我们查看ThreadLocal源码中定义的静态类ThreadLocalMap,其实底层封装的是一个Entry数组,获取方式和普通的HashMap不太一样,如果没有命中,就直接通过线性搜索,因为ThreadLocalMap需要保存的Entry并不会太多。

    1. private Entry getEntry(ThreadLocal<?> key) {
    2. int i = key.threadLocalHashCode & (table.length - 1);
    3. Entry e = table[i];
    4. if (e != null && e.get() == key)
    5. return e;
    6. else
    7. return getEntryAfterMiss(key, i, e);
    8. }
    9. private void set(ThreadLocal<?> key, Object value) {
    10. // We don't use a fast path as with get() because it is at
    11. // least as common to use set() to create new entries as
    12. // it is to replace existing ones, in which case, a fast
    13. // path would fail more often than not.
    14. Entry[] tab = table;
    15. int len = tab.length;
    16. int i = key.threadLocalHashCode & (len-1);
    17. for (Entry e = tab[i];
    18. e != null;
    19. e = tab[i = nextIndex(i, len)]) {
    20. ThreadLocal<?> k = e.get();
    21. if (k == key) {
    22. e.value = value;
    23. return;
    24. }
    25. if (k == null) {
    26. replaceStaleEntry(key, value, i);
    27. return;
    28. }
    29. }

    通过ThreadLocal,每个线程保存自身的数据,不能访问到其他线程的数据。

    ThreadLocalMap的Entry使用ThreadLocal的WeakReference引用作为Key值,当所有线程运行出ThreadLocal的作用域时,即没有强引用ThreadLocal时,ThreadLocal就会被回收。

    1. static class Entry extends WeakReference<ThreadLocal<?>> {
    2. /** The value associated with this ThreadLocal. */
    3. Object value;
    4. Entry(ThreadLocal<?> k, Object v) {
    5. super(k);
    6. value = v;
    7. }
    8. }

    不是Threadlocal为每个线程提供了独立的变量,而是每个线程自己带了自己独立的变量。

    关于内存泄漏
    关于ThreadLocalMap的内存泄漏:如果一个ThreadLocal的生命周期结束,即在ThreadLocal所处的类中没有了强引用,而Thread没有结束,在Thread的threadLocals成员变量中,会有一个Entry使用弱引用引用了ThreadLocal作为key,因为是弱引用,这个key将被回收。而value是强引用,看起来是会造成泄漏,但是在ThreadLocalMap的set和get方法中,有一些释放的方法。具体的我也不太懂。
    还是老老实实使用ThreadLocal的remove方法比较好。

    1. public void remove() {
    2. ThreadLocalMap m = getMap(Thread.currentThread());
    3. if (m != null)
    4. m.remove(this);
    5. }




  • 相关阅读:
    【spring-boot】mybatis-generator 使用入门
    【spring-boot】mybatis 使用入门
    【spring-boot】logback+slf4j 日志组合
    【spring-boot】写一个简单的单元测试
    github执行clone操作时报错
    enable device: BAR 0 [mem 0x00000000-0x003fffff] not claimed
    Meson version is 0.44.1 but project requires >=0.45.
    tig
    Unknown command 'run'
    systemctl enable rc-local.service error
  • 原文地址:https://www.cnblogs.com/Frank-Hao/p/5380307.html
Copyright © 2020-2023  润新知