前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(十五)ThreadLocal的用法,如何解决内存泄漏

(十五)ThreadLocal的用法,如何解决内存泄漏

作者头像
HaC
发布2020-12-30 17:58:41
1.2K0
发布2020-12-30 17:58:41
举报
文章被收录于专栏:HaC的技术专栏HaC的技术专栏

什么是ThreadLocal变量

ThreadLocal称为线程本地变量,其为变量在每个线程中都创建了一个副本,每个线程都访问和修改本线程中变量的副本,但每个线程之间的变量是不能相互访问的,ThreadLocal不是一个Thread。

ThreadLocal 有四个方法:

ThreadLocal方法
ThreadLocal方法

ThreadLocal作用

  1. ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。
  2. 使用同一个threadLocal ,但每个线程的变量是独立都,对其他线程不可见,不需要每个线程都 new 一个对象,减少了内存的开销。

ThreadLocal方法详解

demo:

代码语言:javascript
复制
public class ThreadLocalTest {

    public static void main(String[] args) {

        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    threadLocal.set(1);
                    System.out.println(threadLocal.get());
                }finally {
                    threadLocal.remove();
                }
            }
        }, "Thread1").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(2);
                System.out.println(threadLocal.get());
            }
        }, "Thread2").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLocal.get());
            }
        }, "Thread3").start();
    }

}

输出:

代码语言:javascript
复制
1
2
null

可以看到,每个线程使用都是对同一个threadLocal的引用,但是线程之间的变量是不能互相访问的。

ThreadLocal如何创建副本的?(如何维护变量的)

在每个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。

每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。

也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。

ThreadLocal内存泄漏问题

代码语言:javascript
复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k); //继续深扒,key 是一个弱引用,以一个弱引用指向ThreadLcoal对象k
        value = v;
    }
}

ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收(为什么会被回收下面讲到),然后ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄漏。

以上总结就是,key为null被回收了,但是value还在,需要在set、get的时候判断才能回收。

那为什么value不能被设置成弱引用呢?

如果vaule设计为弱引用,你可能获取到的是null ,毫无意义。

为什么要使用弱引用而不是强引用?

强引用:
代码语言:javascript
复制
Object object= new Object();

强引用用的最多,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

代码语言:javascript
复制
object =null

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,才可以当做垃圾被收集,当然具体回收时机还是要看垃圾收集策略。

而强引用是造成Java内存泄漏的主要原因之一,内存空间不足时,Java虚拟机会抛出OutOfMemoryError ,使程序异常终止,但是注意,抛出错误也不会回收这些强引用的对象。

弱引用:
代码语言:javascript
复制
String str = new String("123");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;

在垃圾回收的一个周期内,jvm发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

所以,弱引用相对强引用来说,生命周期更短。

参考四种引用:https://www.cnblogs.com/yanl55555/p/13365397.html

引用类型

被垃圾回收时间

用途

生存时间

强引用

从来不会

对象的一般状态

JVM停止运行时终止

软引用

当内存不足时

对象缓存

内存不足时终止

弱引用

正常垃圾回收时

对象缓存

垃圾回收后终止

虚引用

正常垃圾回收时

跟踪对象的垃圾回收

垃圾回收后终止

上个图理解一下:

看图,当主线程结束,栈帧销毁,强引用ThreadLocal没有了。再看一下红色部分,

如果是强引用, 线程的ThreadLocalMap里某个entry的 k 引用还指向这个ThreadLocal对象,这样会导致k指向的ThreadLocal对象以及 v 指向的对象都不能被jvm虚拟机gc回收,造成内存泄漏。

如果是弱引用,就可以是ThreadLocal对象在执行完毕的时候被回收了,因为此时只有entry的k弱引用指向它,ThreadLocal回收后,k就指向为null了,但是v还是有值,也会有内存泄漏的风险,并不能保证不会内存泄漏。

那ThreadLocal为什么要使用弱引用而不是强引用呢?

总结 就是是减少严重内存泄漏的风险。

  1. 上面提到,key为弱引用,key为null时,value不为null,导致value无法被回收,引发内存泄漏。
  2. 弱引用尚且有内存泄漏的风险,强引用更加。使用线程池的时候,自定义的线程数不规范,若使用强引用,内存泄漏的风险更高。

如何防止内存泄漏?

上面提到entry的value还会有内存泄漏的风险。

ThreadLocal有通过方法:调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

上面demo的正确例子应该是:

代码语言:javascript
复制
new Thread(new Runnable() {
    @Override
    public void run() {
        try{
            threadLocal.set(1);
            System.out.println(threadLocal.get());
        }finally {
            threadLocal.remove(); //用完要remove,防止内存泄漏
        }
    }
}, "Thread1").start();

remove方法核心:

代码语言:javascript
复制
 private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i); //调用消除方法
                    return;
                }
            }
        }
        
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;   //把vaule赋值null
    tab[staleSlot] = null; 
    size--;

还有set方法:

代码语言:javascript
复制
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);

参考:

https://blog.csdn.net/qq_33404395/article/details/82356344

https://www.cnblogs.com/shen-qian/p/12108655.html## 什么是ThreadLocal变量

ThreadLocal称为线程本地变量,其为变量在每个线程中都创建了一个副本,每个线程都访问和修改本线程中变量的副本,但每个线程之间的变量是不能相互访问的,ThreadLocal不是一个Thread。

ThreadLocal 有四个方法:

ThreadLocal方法
ThreadLocal方法

ThreadLocal作用

  1. ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。
  2. 使用同一个threadLocal ,但每个线程的变量是独立都,对其他线程不可见,不需要每个线程都 new 一个对象,减少了内存的开销。

ThreadLocal方法详解

demo:

代码语言:javascript
复制
public class ThreadLocalTest {

    public static void main(String[] args) {

        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    threadLocal.set(1);
                    System.out.println(threadLocal.get());
                }finally {
                    threadLocal.remove();
                }
            }
        }, "Thread1").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(2);
                System.out.println(threadLocal.get());
            }
        }, "Thread2").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLocal.get());
            }
        }, "Thread3").start();
    }

}

输出:

代码语言:javascript
复制
1
2
null

可以看到,每个线程使用都是对同一个threadLocal的引用,但是线程之间的变量是不能互相访问的。

ThreadLocal如何创建副本的?(如何维护变量的)

在每个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。

每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。

也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。

ThreadLocal内存泄漏问题

代码语言:javascript
复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k); //继续深扒,key 是一个弱引用,以一个弱引用指向ThreadLcoal对象k
        value = v;
    }
}

ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收(为什么会被回收下面讲到),然后ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄漏。

以上总结就是,key为null被回收了,但是value还在,需要在set、get的时候判断才能回收。

那为什么value不能被设置成弱引用呢?

如果vaule设计为弱引用,你可能获取到的是null ,毫无意义。

为什么要使用弱引用而不是强引用?

强引用:
代码语言:javascript
复制
Object object= new Object();

强引用用的最多,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

代码语言:javascript
复制
object =null

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,才可以当做垃圾被收集,当然具体回收时机还是要看垃圾收集策略。

而强引用是造成Java内存泄漏的主要原因之一,内存空间不足时,Java虚拟机会抛出OutOfMemoryError ,使程序异常终止,但是注意,抛出错误也不会回收这些强引用的对象。

弱引用:
代码语言:javascript
复制
String str = new String("123");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;

在垃圾回收的一个周期内,jvm发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

所以,弱引用相对强引用来说,生命周期更短。

参考四种引用:https://www.cnblogs.com/yanl55555/p/13365397.html

引用类型

被垃圾回收时间

用途

生存时间

强引用

从来不会

对象的一般状态

JVM停止运行时终止

软引用

当内存不足时

对象缓存

内存不足时终止

弱引用

正常垃圾回收时

对象缓存

垃圾回收后终止

虚引用

正常垃圾回收时

跟踪对象的垃圾回收

垃圾回收后终止

上个图理解一下:

看图,当主线程结束,栈帧销毁,强引用ThreadLocal没有了。再看一下红色部分,

如果是强引用, 线程的ThreadLocalMap里某个entry的 k 引用还指向这个ThreadLocal对象,这样会导致k指向的ThreadLocal对象以及 v 指向的对象都不能被jvm虚拟机gc回收,造成内存泄漏。

如果是弱引用,就可以是ThreadLocal对象在执行完毕的时候被回收了,因为此时只有entry的k弱引用指向它,ThreadLocal回收后,k就指向为null了,但是v还是有值,也会有内存泄漏的风险,并不能保证不会内存泄漏。

那ThreadLocal为什么要使用弱引用而不是强引用呢?

总结 就是是减少严重内存泄漏的风险。

  1. 上面提到,key为弱引用,key为null时,value不为null,导致value无法被回收,引发内存泄漏。
  2. 弱引用尚且有内存泄漏的风险,强引用更加。使用线程池的时候,自定义的线程数不规范,若使用强引用,内存泄漏的风险更高。

如何防止内存泄漏?

上面提到entry的value还会有内存泄漏的风险。

ThreadLocal有通过方法:调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

上面demo的正确例子应该是:

代码语言:javascript
复制
new Thread(new Runnable() {
    @Override
    public void run() {
        try{
            threadLocal.set(1);
            System.out.println(threadLocal.get());
        }finally {
            threadLocal.remove(); //用完要remove,防止内存泄漏
        }
    }
}, "Thread1").start();

remove方法核心:

代码语言:javascript
复制
 private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i); //调用消除方法
                    return;
                }
            }
        }
        
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;   //把vaule赋值null
    tab[staleSlot] = null; 
    size--;

还有set方法:

代码语言:javascript
复制
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);

参考:

https://blog.csdn.net/qq_33404395/article/details/82356344

https://www.cnblogs.com/shen-qian/p/12108655.html## 什么是ThreadLocal变量

ThreadLocal称为线程本地变量,其为变量在每个线程中都创建了一个副本,每个线程都访问和修改本线程中变量的副本,但每个线程之间的变量是不能相互访问的,ThreadLocal不是一个Thread。

ThreadLocal 有四个方法:

ThreadLocal方法
ThreadLocal方法

ThreadLocal作用

  1. ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。
  2. 使用同一个threadLocal ,但每个线程的变量是独立都,对其他线程不可见,不需要每个线程都 new 一个对象,减少了内存的开销。

ThreadLocal方法详解

demo:

代码语言:javascript
复制
public class ThreadLocalTest {

    public static void main(String[] args) {

        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    threadLocal.set(1);
                    System.out.println(threadLocal.get());
                }finally {
                    threadLocal.remove();
                }
            }
        }, "Thread1").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(2);
                System.out.println(threadLocal.get());
            }
        }, "Thread2").start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLocal.get());
            }
        }, "Thread3").start();
    }

}

输出:

代码语言:javascript
复制
1
2
null

可以看到,每个线程使用都是对同一个threadLocal的引用,但是线程之间的变量是不能互相访问的。

ThreadLocal如何创建副本的?(如何维护变量的)

在每个Thread中包含一个ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的对象,value是独享数据。

每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。

也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。

ThreadLocal内存泄漏问题

代码语言:javascript
复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k); //继续深扒,key 是一个弱引用,以一个弱引用指向ThreadLcoal对象k
        value = v;
    }
}

ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收(为什么会被回收下面讲到),然后ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,造成内存泄漏。

以上总结就是,key为null被回收了,但是value还在,需要在set、get的时候判断才能回收。

那为什么value不能被设置成弱引用呢?

如果vaule设计为弱引用,你可能获取到的是null ,毫无意义。

为什么要使用弱引用而不是强引用?

强引用:
代码语言:javascript
复制
Object object= new Object();

强引用用的最多,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

代码语言:javascript
复制
object =null

对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,才可以当做垃圾被收集,当然具体回收时机还是要看垃圾收集策略。

而强引用是造成Java内存泄漏的主要原因之一,内存空间不足时,Java虚拟机会抛出OutOfMemoryError ,使程序异常终止,但是注意,抛出错误也不会回收这些强引用的对象。

弱引用:
代码语言:javascript
复制
String str = new String("123");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;

在垃圾回收的一个周期内,jvm发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

所以,弱引用相对强引用来说,生命周期更短。

参考四种引用:https://www.cnblogs.com/yanl55555/p/13365397.html

引用类型

被垃圾回收时间

用途

生存时间

强引用

从来不会

对象的一般状态

JVM停止运行时终止

软引用

当内存不足时

对象缓存

内存不足时终止

弱引用

正常垃圾回收时

对象缓存

垃圾回收后终止

虚引用

正常垃圾回收时

跟踪对象的垃圾回收

垃圾回收后终止

上个图理解一下:

看图,当主线程结束,栈帧销毁,强引用ThreadLocal没有了。再看一下红色部分,

如果是强引用, 线程的ThreadLocalMap里某个entry的 k 引用还指向这个ThreadLocal对象,这样会导致k指向的ThreadLocal对象以及 v 指向的对象都不能被jvm虚拟机gc回收,造成内存泄漏。

如果是弱引用,就可以是ThreadLocal对象在执行完毕的时候被回收了,因为此时只有entry的k弱引用指向它,ThreadLocal回收后,k就指向为null了,但是v还是有值,也会有内存泄漏的风险,并不能保证不会内存泄漏。

那ThreadLocal为什么要使用弱引用而不是强引用呢?

总结 就是是减少严重内存泄漏的风险。

  1. 上面提到,key为弱引用,key为null时,value不为null,导致value无法被回收,引发内存泄漏。
  2. 弱引用尚且有内存泄漏的风险,强引用更加。使用线程池的时候,自定义的线程数不规范,若使用强引用,内存泄漏的风险更高。

如何防止内存泄漏?

上面提到entry的value还会有内存泄漏的风险。

ThreadLocal有通过方法:调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

上面demo的正确例子应该是:

代码语言:javascript
复制
new Thread(new Runnable() {
    @Override
    public void run() {
        try{
            threadLocal.set(1);
            System.out.println(threadLocal.get());
        }finally {
            threadLocal.remove(); //用完要remove,防止内存泄漏
        }
    }
}, "Thread1").start();

remove方法核心:

代码语言:javascript
复制
 private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i); //调用消除方法
                    return;
                }
            }
        }
        
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;   //把vaule赋值null
    tab[staleSlot] = null; 
    size--;

还有set方法:

代码语言:javascript
复制
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);

参考:

https://blog.csdn.net/qq_33404395/article/details/82356344

https://www.cnblogs.com/shen-qian/p/12108655.html

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-07-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是ThreadLocal变量
  • ThreadLocal作用
  • ThreadLocal方法详解
  • ThreadLocal如何创建副本的?(如何维护变量的)
  • ThreadLocal内存泄漏问题
  • 为什么要使用弱引用而不是强引用?
    • 强引用:
      • 弱引用:
      • 如何防止内存泄漏?
      • ThreadLocal作用
      • ThreadLocal方法详解
      • ThreadLocal如何创建副本的?(如何维护变量的)
      • ThreadLocal内存泄漏问题
      • 为什么要使用弱引用而不是强引用?
        • 强引用:
          • 弱引用:
          • 如何防止内存泄漏?
          • ThreadLocal作用
          • ThreadLocal方法详解
          • ThreadLocal如何创建副本的?(如何维护变量的)
          • ThreadLocal内存泄漏问题
          • 为什么要使用弱引用而不是强引用?
            • 强引用:
              • 弱引用:
              • 如何防止内存泄漏?
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
              http://www.vxiaotou.com