ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Java多线程——ThreadLocal介绍以及内存泄漏问题

2022-01-16 10:31:26  阅读:166  来源: 互联网

标签:null Java value ThreadLocal 线程 引用 key 多线程


ThreadLocal

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

ThreadLocal 有四个方法:
在这里插入图片描述

ThreadLocal作用

ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降。

使用同一个threadLocal ,但每个线程的变量是独立都,对其他线程不可见,不需要每个线程都 new 一个对象,减少了内存的开销。

ThreadLocal方法详解

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();
    }

}
1
2
null

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

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

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

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

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

ThreadLocal内存泄漏问题

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 ,毫无意义。

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

强引用:

Object object= new Object();

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

object =null

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

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

弱引用:

String str = new String("123");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;

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

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

参考四种引用:https://www.cnblogs.com/yanl55555/p/13365397.html
在这里插入图片描述
上个图理解一下:
在这里插入图片描述
看图,当主线程结束,栈帧销毁,强引用ThreadLocal没有了。再看一下红色部分,

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

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

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

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

上面提到,key为弱引用,key为null时,value不为null,导致value无法被回收,引发内存泄漏。

弱引用尚且有内存泄漏的风险,强引用更加。使用线程池的时候,自定义的线程数不规范,若使用强引用,内存泄漏的风险更高。

如何防止内存泄漏?

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

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

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

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

remove方法核心:

 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方法:

// 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

标签:null,Java,value,ThreadLocal,线程,引用,key,多线程
来源: https://blog.csdn.net/qq_33333654/article/details/122519704

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有