java基础-ThreadLocal

来聊一下ThreadLocal的实现原理和它的内存泄漏问题

首先来看一个官方示例,这里构造了一个ThreadId类,其作用是在每个线程中保存各自的id,此id全局唯一,通过get可以获取id。

 1     private static class ThreadId {
 2         // Atomic integer containing the next thread ID to be assigned
 3         private static final AtomicInteger nextId = new AtomicInteger(1);
 4         // Thread local variable containing each thread‘s ID
 5         private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
 6             @Override
 7             protected Integer initialValue() {
 8                 return nextId.getAndIncrement();
 9             }
10         };
11         // Returns the current thread‘s unique ID, assigning it if necessary
12         public static int get() {
13             return threadId.get();
14         }
15     }

ThreadLocal的构造器是一个空函数,new一个ThreadLocal实例时,唯一的操作就是对threadLocalHashCode的初始化,很明显这是一个hash值,猜测后续会用到map了。

1 private final int threadLocalHashCode = nextHashCode();

再来看调用get时,发生了什么

/**
     * Returns the value in the current thread‘s copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread‘s value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        //Thread中有一个ThreadLocalMap类型的实例字段,获得当前线程的threadLocalMap,其实就是返回t.threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //以当前ThreadLocal实例为key,获取entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //得到entry中的value
                T result = (T)e.value;
                return result;
            }
        }
        //如果当前线程的threadLocalMap为null,或者当前threadLocal还未插入threadLocalMap,则进行相应初始化工作,无非就是初始化map、调用自定义的initialValue方法、将initialValue返回值包装成entry插入map
        return setInitialValue();
    }

看一下map的构造

 1     static class ThreadLocalMap {
 2         ......
 3         static class Entry extends WeakReference<ThreadLocal<?>> {
 4             /** The value associated with this ThreadLocal. */
 5             Object value;
 6
 7             Entry(ThreadLocal<?> k, Object v) {
 8                 //注意,这里key被包装成了一个WeakReference
 9                 super(k);
10                 value = v;
11             }
12         }
13         ......
14         private Entry[] table;
15         ......
16         private Entry getEntry(ThreadLocal<?> key) {
17             //threadLocalHashCode在ThreadLocal初始化时就已生成,全局唯一。这里
18             int i = key.threadLocalHashCode & (table.length - 1);
19             Entry e = table[i];
20             if (e != null && e.get() == key)//e.get()即从WeakReference中取得threadLocal
21                 return e;
22             else
23                 //有意思,一般的hash表都是使用一个链式结构来解决hash冲突,而这里当hash冲突时进行线性探测
24                 return getEntryAfterMiss(key, i, e);
25         }
26         ......
27     }

问题来了,为什么entry中的key要包装成WeakReference呢?

设想,当我们不再需要threadLocal了,以前例来说就是置ThreadId中类变量threadId为null(假设threadId不是final,也没有被其他引用),而Thread类中的threadLocalMap中仍然持有threadId的引用,这就会产生内存泄漏。将threadLocal包装成WeakReference作为key存储,当threadId为null时,该threadLocal在gc时就会被回收,但此时value还在,ThreadLocal会在进行其他操作时删除key为null的value。这确实存在一种内存泄漏隐患,如果之后不在进行ThreadLocal操作,就真释放不掉value了。通常我们需要被声明为ThreadLocal的变量,在运行期间都是不期望它被回收的,所以我们通常会将其声明为static final,如果有回收的需求,也请使用ThreadLocal的remove进行显示释放。

时间: 2024-10-26 17:36:36

java基础-ThreadLocal的相关文章

不惑JAVA之JAVA基础 - ThreadLocal

ThreadLocal在数据库连接和session管理下有广泛的应用,了解ThreadLocal对struts.spring等开源代码的理解有很大的帮助. ThreadLocal如果单纯从名字上来看像是"本地线程"这么个意思,只能说这个名字起的确实不太好,很容易让人产生误解,ThreadLocalVariable(线程本地变量)应该是个更好的名字.我们先看一下官方对ThreadLocal的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因

java基础-ThreadLocal变量和普通变量的区别

java提供了ThreadLocal这个类型,具有该类型的成员变量,每个使用到该变量的线程都保留一份该属性的备份数据,在线程内部对该属性的操作都是自己备份的数据,所以声明为ThreadLocal类型的成员变量都是线程安全的. 简单测试了一下ThreadLocal类型的成员和普通成员的区别,在多线程环境,每个线程都会存有一个ThreadLocal的值,而普通成员则是线程共享的. import java.util.Date; public class MyThreadLocal { private

Java基础】并发 - 多线程

Java基础]并发 - 多线程 分类: Java2014-05-03 23:56 275人阅读 评论(0) 收藏 举报 Java 目录(?)[+] 介绍 Java多线程 多线程任务执行 大多数并发应用程序时围绕执行任务(task)进行管理的:所谓任务就是抽象的,离散的工作单元. 围绕执行任务来管理应用程序时,第一步是要指明一个清晰的任务边界.大多数应用服务器程序都选择了下面这个自然的任务辩解:单独的客户请求: 任务时逻辑上的单元: 任务 Runnable 表示一个任务单元(java.lang)

java基础-java核心知识库

1.Spring.mvc的优势,原理,流程 2.Mybatis的原理优势 3.集合里面那些对象的原理 4.扩容原理,特别是map的底层 5.Hashmap.Hashtable和cocurrentHashMap的区别,要讲出它们各自的实现原理才行,比如Hashmap的扩容机制.cocurrentHashMap的段锁原理.多线程安全性. 6.几种造线程池的方法,区别 7.线程有哪几种状态,他们是如何转换的 8.Rpc原理,以及大致流程 9.Nio和netty的区别,为什么netty的性能高,nio,

【java基础之jdk源码】Object

最新在整体回归下java基础薄弱环节,以下为自己整理笔记,若有理解错误,请批评指正,谢谢. java.lang.Object为java所有类的基类,所以一般的类都可用重写或直接使用Object下方法,以下为逻辑结构图,没有画类图 (注: 以上绿色方法为 非native方法  粉色方法为 native方法) 那么问题来了 : 1.what is a native object? 本人理解: native关键字标识的java方法为本地方法,底层是有c/c++编写的程序编译后dll文件,java加载d

JAVA基础-02

Java学习笔记 Java基础  基本类型不是new出来的则是放在栈里面,对象的引用也是放在栈里面的,只要是用new()来新建对象的,都会在堆中创建 String类被设计成为不可改变(immutable)的类.如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用.这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间.在对时间要求比较敏感的环境中,会带有一定的不良影响. 关于String str = "abc"的内部工作.Ja

学习Spring必学的Java基础知识(2)----动态代理

学习Spring必学的Java基础知识(2)----动态代理 引述要学习Spring框架的技术内幕,必须事先掌握一些基本的Java知识,正所谓“登高必自卑,涉远必自迩”.以下几项Java知识和Spring框架息息相关,不可不学(我将通过一个系列分别介绍这些Java基础知识,希望对大家有所帮助.): [1] Java反射知识-->Spring IoC :http://www.iteye.com/topic/1123081 [2] Java动态代理-->Spring AOP :http://www

Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)

线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public class Thread implements Runnable { /* What will be run. */ private Runnable target; ...... /** * Causes this thread to begin execution; the Java Virtu

Java基础总结(内部版)【转】

一.JVM 1.内存模型 1.1.1 内存分几部分 (1)程序计数器 可看作当前线程所执行的字节码的**行号指示器**.字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支.循环.跳转.异常处理.线程恢复等基础功能都需要依赖这个计数器来完成. 在线程创建时创建.执行本地方法时,PC的值为null.为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,线程私有. (2)Java虚拟机栈 线程私有,生命周期同线程.**每个方法在执行同时,创建栈帧*