ThreadLocal源码分析&内存泄漏详解

1. ThreadLocal概述


ThreadLocal是多线程中解决线程安全的一个类,它会为每一个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。
ThreadLocal实现了线程内的资源共享。

例如:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的Connection上进行数据库的操作,避免了A线程关闭B线程的连接。

2. ThreadLocal源码


2.1 基本使用


ThreadLocal:

  1. set(value)设置值
  2. get()获取值
  3. remove()清除值

2.2 源码分析


ThreadLocal类是一个带有泛型参数的类,定义时需要表明在ThreadLocal中存储的数据类型才行。

2.2.1 ThreadLocal数据存储


ThreadLocal中数据的存储都是存储在ThreadLocalMap(定义在ThreadLoca中,是其内部静态类)中的,默认容量为16.

1. ThreadLocal概述


ThreadLocal是多线程中解决线程安全的一个类,它会为每一个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。
ThreadLocal实现了线程内的资源共享。

例如:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的Connection上进行数据库的操作,避免了A线程关闭B线程的连接。

2. ThreadLocal源码


2.1 基本使用


ThreadLocal:

  1. set(value)设置值
  2. get()获取值
  3. remove()清除值

2.2 源码分析


ThreadLocal类是一个带有泛型参数的类,定义时需要表明在ThreadLocal中存储的数据类型才行。

2.2.1 ThreadLocal数据存储


ThreadLocal中数据的存储都是存储在ThreadLocalMap(定义在ThreadLoca中,是其内部静态类)中的,默认容量为16.
![[Pasted image 20240807151339.png]]
ThreadLocalMap定义的部分源码如下:

static class ThreadLocalMap {  
  
    /**  
     * The entries in this hash map extend WeakReference, using     * its main ref field as the key (which is always a     * ThreadLocal object).  Note that null keys (i.e. entry.get()     * == null) mean that the key is no longer referenced, so the     * entry can be expunged from table.  Such entries are referred to     * as "stale entries" in the code that follows.     */    static class Entry extends WeakReference<ThreadLocal<?>> {  
        /** The value associated with this ThreadLocal. */  
        Object value;  
  
        Entry(ThreadLocal<?> k, Object v) {  
            super(k);  
            value = v;  
        }  
    }  
  
    /**  
     * The initial capacity -- MUST be a power of two.     */    
     private static final int INITIAL_CAPACITY = 16;  
  
    /**  
     * The table, resized as necessary.     * table.length MUST always be a power of two.     */    
     private Entry[] table;  
  
    /**  
     * The number of entries in the table.     */    
     private int size = 0;  
  
    /**  
     * The next size value at which to resize.     */    
     private int threshold; // Default to 0  
  
    /**  
     * Set the resize threshold to maintain at worst a 2/3 load factor.     */    
     private void setThreshold(int len) {  
        threshold = len * 2 / 3;  
    }  
  
    /**  
     * Increment i modulo len.     */    
    private static int nextIndex(int i, int len) {  
        return ((i + 1 < len) ? i + 1 : 0);  
    }  
  
    /**  
     * Decrement i modulo len.     */    
    private static int prevIndex(int i, int len) {  
        return ((i - 1 >= 0) ? i - 1 : len - 1);  
    }  
  
    /**  
     * Construct a new map without a table.     */    
     private ThreadLocalMap() {  }
     /**  
 * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */
	 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对象,来逐步分析set和get方法

package ThreadLocal;  
public class test {  
    public static void main(String[] args){  
        ThreadLocal<String> a=new ThreadLocal<>();  
        String name=Thread.currentThread().getName();  
        a.set("Test");  
        System.out.println(name+":"+a.get());  
        a.remove();  
        System.out.println(name+":"+a.get());  
    }  
}

运行结果:
![[Pasted image 20240807150057.png]]

2.2.2 执行Set方法流程


Set方法源码:

public void set(T value) {  
	//获取当前线程,并作为参数
    set(Thread.currentThread(), value);  
    //
    if (TRACE_VTHREAD_LOCALS) {  
        dumpStackIfVirtualThread();  
    }  
}

当在执行Set方法时会进入重载的set(Thread t, T value); 方法中

源码如下:

private void set(Thread t, T value) {  
	//根据当前线程对象,获取ThreadLocal中的ThreadLocalMap
    ThreadLocalMap map = getMap(t);  
    //如果Map存在
    if (map != null) {
    //执行Map中的set方法,进行数据存储  
        map.set(this, value);  
    } else {  
    //否则进行创建ThreadLocalMap,并存值
        createMap(t, value);  
    }  
}

在第一次进行插入值时,会先进入createMap(t, value); 函数。
于是便会在该线程中创建一个ThreadLocalMap,this并不是t,而是在ThreadLocal类中定义的,应该是线程相关联的属性。

void createMap(Thread t, T firstValue) {  
    t.threadLocals = new ThreadLocalMap(this, firstValue);  
}

构造函数如下所示:

/**  
 * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */
	 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {  
	 //内部成员数组,INITIAL_CAPACITY值为16的常量
	    table = new Entry[INITIAL_CAPACITY];  
	    //位运算,结果与取模相同,计算出需要存放的位置
	    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  
	    //firstKey是ThreadLocal,firstValue存储的是值,将元素插入到table[i]上
	    table[i] = new Entry(firstKey, firstValue);  
	    size = 1;  
	    setThreshold(INITIAL_CAPACITY);  
	}

当进行插入第二个元素时,会去执行map.set(this, value);
源码如下:

/**  
 * Set the value associated with key. * * @param key the thread local object  
 * @param value the value to be set  
 */
 private void set(ThreadLocal<?> key, Object value) {  
    // We don't use a fast path as with get() because it is at  
    // least as common to use set() to create new entries as    // it is to replace existing ones, in which case, a fast    // path would fail more often than not.  
    //获取到存储数据Table表
    Entry[] tab = table;
    //获取Tabel长度进行取模运算
    int len = tab.length;  
    int i = key.threadLocalHashCode & (len-1);  
  //用来遍历从索引`i`开始的数组元素,直到找到空位或者需要替换的元素。`nextIndex(i, len)`用于计算下一个索引,防止数组越界。
    for (Entry e = tab[i];   e != null;  e = tab[i = nextIndex(i, len)]) 
    {  
        if (e.refersTo(key)) {  
            e.value = value;  
            return;  
        }  
  
        if (e.refersTo(null)) {  
            replaceStaleEntry(key, value, i);  
            return;  
        }  
    }  
  
    tab[i] = new Entry(key, value);  
    int sz = ++size;  
    if (!cleanSomeSlots(i, sz) && sz >= threshold)  
        rehash();  
}

2.2.3 get()/Remove()方法


测试代码:

public class test {  
    public static void main(String[] args){  
        ThreadLocal<String> a=new ThreadLocal<>();  
        a.set("Test");   
        String test=a.get();  
	    System.out.println(test);
    }  
}

当执行到get方法时,会进入下面的源码:

private T get(Thread t) {  
    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(t);  
}

进而执行map.getEntry(this); 源码如下:

private Entry getEntry(ThreadLocal<?> key) {  
	//确认数组下标位置
    int i = key.threadLocalHashCode & (table.length - 1);  
    //得到该位置的Entry
    Entry e = table[i];  
    if (e != null && e.refersTo(key))  
        return e;  
    else  
        return getEntryAfterMiss(key, i, e);  
}

Remove方法是类似与get()方法的,只是将Table[i]设置位null即可

2.3 总结


可以说在ThreadLocalMap中存储Key是定义的ThreadLocal对象,Value是实际设置的值。
![[2e83901a39f94e2fbc9a529df39de9c.jpg]]

3. ThreadLocal内存泄漏问题


3.1 引用类型


3.1.1 强引用


强引用(StrongReference),是 Java 的默认引用实现,是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它,它会尽可能长时间的存活于 JVM 内。

没有任何对象指向它时,该对象才可以被垃圾回收。
![[Pasted image 20240807160510.png]]

3.1.2 软引用


软引用( SoftReference) 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存应用
如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中

通过第二行代码,将user的强引用转换为软引用(需要SoftReference来配合使用)。
![[Pasted image 20240807160851.png]]

3.1.3. 弱引用


弱引用(WeakReference), 顾名思义, 是一个弱引用, 当所引用的对象在 JVM 内不再有强引用时, GCweak reference 将会被自动回收,如果一个对象只具有弱引用,那就类似于可有可物的生活用品。

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
![[Pasted image 20240807161253.png]]

ThreadLocal内存泄漏问题就出现在这里:
Entry对象的构造方法中,第一个ThreadLocal《?》 k 是弱引用,当发生垃圾回收时,就想要回收这个Entry对象,但是value=v;是强引用,又不会被回收,于是便发生了内存泄漏(本应该进行垃圾回收的对象,没有被回收)问题。

3.1.4 虚引用


虚引用(PhantomReference) 与 WeakReference 和 SoftReference 有很大的不同, 因为它的 get() 方法永远返回 null, 这也正是它名字的由来,与其他几种引用都不同,虚引用并不会决定对象的生命 周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用作用:用来跟踪对象被垃圾回收的活动

虚引用:必须配合引用队列使用,被引用对象回收时,会将虚引用入队,有Reference Handler线程调度虚引用相关方法释放内存。

ThreadLocalMap定义的部分源码如下:

static class ThreadLocalMap {  
  
    /**  
     * The entries in this hash map extend WeakReference, using     * its main ref field as the key (which is always a     * ThreadLocal object).  Note that null keys (i.e. entry.get()     * == null) mean that the key is no longer referenced, so the     * entry can be expunged from table.  Such entries are referred to     * as "stale entries" in the code that follows.     */    static class Entry extends WeakReference<ThreadLocal<?>> {  
        /** The value associated with this ThreadLocal. */  
        Object value;  
  
        Entry(ThreadLocal<?> k, Object v) {  
            super(k);  
            value = v;  
        }  
    }  
  
    /**  
     * The initial capacity -- MUST be a power of two.     */    
     private static final int INITIAL_CAPACITY = 16;  
  
    /**  
     * The table, resized as necessary.     * table.length MUST always be a power of two.     */    
     private Entry[] table;  
  
    /**  
     * The number of entries in the table.     */    
     private int size = 0;  
  
    /**  
     * The next size value at which to resize.     */    
     private int threshold; // Default to 0  
  
    /**  
     * Set the resize threshold to maintain at worst a 2/3 load factor.     */    
     private void setThreshold(int len) {  
        threshold = len * 2 / 3;  
    }  
  
    /**  
     * Increment i modulo len.     */    
    private static int nextIndex(int i, int len) {  
        return ((i + 1 < len) ? i + 1 : 0);  
    }  
  
    /**  
     * Decrement i modulo len.     */    
    private static int prevIndex(int i, int len) {  
        return ((i - 1 >= 0) ? i - 1 : len - 1);  
    }  
  
    /**  
     * Construct a new map without a table.     */    
     private ThreadLocalMap() {  }
     /**  
 * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */
	 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对象,来逐步分析set和get方法

package ThreadLocal;  
public class test {  
    public static void main(String[] args){  
        ThreadLocal<String> a=new ThreadLocal<>();  
        String name=Thread.currentThread().getName();  
        a.set("Test");  
        System.out.println(name+":"+a.get());  
        a.remove();  
        System.out.println(name+":"+a.get());  
    }  
}

运行结果:
![[Pasted image 20240807150057.png]]

2.2.2 执行Set方法流程


Set方法源码:

public void set(T value) {  
	//获取当前线程,并作为参数
    set(Thread.currentThread(), value);  
    //
    if (TRACE_VTHREAD_LOCALS) {  
        dumpStackIfVirtualThread();  
    }  
}

当在执行Set方法时会进入重载的set(Thread t, T value); 方法中

源码如下:

private void set(Thread t, T value) {  
	//根据当前线程对象,获取ThreadLocal中的ThreadLocalMap
    ThreadLocalMap map = getMap(t);  
    //如果Map存在
    if (map != null) {
    //执行Map中的set方法,进行数据存储  
        map.set(this, value);  
    } else {  
    //否则进行创建ThreadLocalMap,并存值
        createMap(t, value);  
    }  
}

在第一次进行插入值时,会先进入createMap(t, value); 函数。
于是便会在该线程中创建一个ThreadLocalMap,this并不是t,而是在ThreadLocal类中定义的,应该是线程相关联的属性。

void createMap(Thread t, T firstValue) {  
    t.threadLocals = new ThreadLocalMap(this, firstValue);  
}

构造函数如下所示:

/**  
 * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */
	 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {  
	 //内部成员数组,INITIAL_CAPACITY值为16的常量
	    table = new Entry[INITIAL_CAPACITY];  
	    //位运算,结果与取模相同,计算出需要存放的位置
	    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  
	    //firstKey是ThreadLocal,firstValue存储的是值,将元素插入到table[i]上
	    table[i] = new Entry(firstKey, firstValue);  
	    size = 1;  
	    setThreshold(INITIAL_CAPACITY);  
	}

当进行插入第二个元素时,会去执行map.set(this, value);
源码如下:

/**  
 * Set the value associated with key. * * @param key the thread local object  
 * @param value the value to be set  
 */
 private void set(ThreadLocal<?> key, Object value) {  
    // We don't use a fast path as with get() because it is at  
    // least as common to use set() to create new entries as    // it is to replace existing ones, in which case, a fast    // path would fail more often than not.  
    //获取到存储数据Table表
    Entry[] tab = table;
    //获取Tabel长度进行取模运算
    int len = tab.length;  
    int i = key.threadLocalHashCode & (len-1);  
  //用来遍历从索引`i`开始的数组元素,直到找到空位或者需要替换的元素。`nextIndex(i, len)`用于计算下一个索引,防止数组越界。
    for (Entry e = tab[i];   e != null;  e = tab[i = nextIndex(i, len)]) 
    {  
        if (e.refersTo(key)) {  
            e.value = value;  
            return;  
        }  
  
        if (e.refersTo(null)) {  
            replaceStaleEntry(key, value, i);  
            return;  
        }  
    }  
  
    tab[i] = new Entry(key, value);  
    int sz = ++size;  
    if (!cleanSomeSlots(i, sz) && sz >= threshold)  
        rehash();  
}

2.2.3 get()/Remove()方法


测试代码:

public class test {  
    public static void main(String[] args){  
        ThreadLocal<String> a=new ThreadLocal<>();  
        a.set("Test");   
        String test=a.get();  
	    System.out.println(test);
    }  
}

当执行到get方法时,会进入下面的源码:

private T get(Thread t) {  
    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(t);  
}

进而执行map.getEntry(this); 源码如下:

private Entry getEntry(ThreadLocal<?> key) {  
	//确认数组下标位置
    int i = key.threadLocalHashCode & (table.length - 1);  
    //得到该位置的Entry
    Entry e = table[i];  
    if (e != null && e.refersTo(key))  
        return e;  
    else  
        return getEntryAfterMiss(key, i, e);  
}

Remove方法是类似与get()方法的,只是将Table[i]设置位null即可

2.3 总结


可以说在ThreadLocalMap中存储Key是定义的ThreadLocal对象,Value是实际设置的值。
![[2e83901a39f94e2fbc9a529df39de9c.jpg]]

3. ThreadLocal内存泄漏问题


3.1 引用类型


3.1.1 强引用


强引用(StrongReference),是 Java 的默认引用实现,是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它,它会尽可能长时间的存活于 JVM 内。

没有任何对象指向它时,该对象才可以被垃圾回收。
![[Pasted image 20240807160510.png]]

3.1.2 软引用


软引用( SoftReference) 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存应用
如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中

通过第二行代码,将user的强引用转换为软引用(需要SoftReference来配合使用)。
![[Pasted image 20240807160851.png]]

3.1.3. 弱引用


弱引用(WeakReference), 顾名思义, 是一个弱引用, 当所引用的对象在 JVM 内不再有强引用时, GCweak reference 将会被自动回收,如果一个对象只具有弱引用,那就类似于可有可物的生活用品。

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
![[Pasted image 20240807161253.png]]

ThreadLocal内存泄漏问题就出现在这里:
Entry对象的构造方法中,第一个ThreadLocal《?》 k 是弱引用,当发生垃圾回收时,就想要回收这个Entry对象,但是value=v;是强引用,又不会被回收,于是便发生了内存泄漏(本应该进行垃圾回收的对象,没有被回收)问题。

3.1.4 虚引用


虚引用(PhantomReference) 与 WeakReference 和 SoftReference 有很大的不同, 因为它的 get() 方法永远返回 null, 这也正是它名字的由来,与其他几种引用都不同,虚引用并不会决定对象的生命 周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用作用:用来跟踪对象被垃圾回收的活动

虚引用:必须配合引用队列使用,被引用对象回收时,会将虚引用入队,有Reference Handler线程调度虚引用相关方法释放内存。

  • 15
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值