用ThreadLocal征服她

在java的多线程模块中,ThreadLocal是经常被提问到的一个知识点,提问的方式有很多种,但是基本流程都是一样的,因为他的分析都是一步一步过来的,有因有果。

这篇文章主要从以下几个角度来分析理解


前言

提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、ThreadLocal是什么?

ThreadLocal其实就是基于字面意思上理解就是线程局部变量,顾名思义则是线程安全的,因为都是局部了嘛。意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

二、ThreadLocal 如何使用

代码如下(示例):


    public static void main(String[] args) {
        ThreadLocal<Object> threadLocal = new ThreadLocal<>();

        threadLocal.set("123");
        new Thread(()->{
            threadLocal.set("456");
            System.out.println(Thread.currentThread().getName()+"----"+threadLocal.get());
        },"t1").start();
        System.out.println(Thread.currentThread().getName()+"----"+threadLocal.get());
    }

运行结果:
main----123
t1----456
ThreadLocal的作用是每一个线程创建一个副本,从结果中我们看到,各个的线程互相持有不同的值。这是ThreadLocal的最基本的使用,其实他应用方面好多,比如Spring事务,多数据源等,看下面的代码你就对它的应用场景有一定的了解了

public class ConnectionManager {

    private static Connection connection = null;


    public static Connection getConnection() throws Exception{
        if(connection == null){
            connection = DriverManager.getConnection("jdbc","username","password");

        }
        return connection;
    }

    public static void closeConnection()throws Exception{
        if(connection != null){
            connection.close();
        }
    }
}

这是一个获取数据链接的方法,如果一个线程经常获取链接,关闭链接会给服务器造成很大的压力所以得采用连接池的思想:应用ThreadLocal 将数据库链接当作局部变量赋值给每个线程,这样每个线程在使用到的时候就不用频繁创建了,而且线程之间互不影响,也不会存在现在安全问题

三、ThreadLocal-set方法

代码如下(示例):

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

关键的几句介绍下:首先获取当前线程,通过当前线程获取ThreadLocalMap对象,如果这对象为空,则创建,执行createMap方法,否则执行set方法;

set方法 看到这个方法大家应该就懂了 ThreadLocal中的key是谁了吧!就是当前this对象,ThreadLocal,但是他的value可不是单纯的Object对象,而是一个ThreadLocalMap,这个对象可以说是ThreadLocal中的核心了

 private void set(ThreadLocal<?> key, Object value) {
            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)]) {
                ThreadLocal<?> k = e.get();
                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                	//将这个value包装ThreadLocalMap.entry对象
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

四、ThreadLocal-get方法

看完get方法你对ThreadLocal基本可以说是入门了,也知道了最终保存的value就是一个ThreadLocalMap对象中Entry属性。

public T get() {
        Thread t = Thread.currentThread();
        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();
    }

简单介绍下 setInitialValue方法,看上面的代码也知道他是在map对象为空的情况下,执行此方法

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

最终 执行
protected T initialValue() {
return null;
}
这个方法是用来子类实现,初始化用。比如SimpleDateFormat 他不是一个线程安全的类,在多线程使用中会有问题,一般这样用它

        ThreadLocal<Object> objectThreadLocal = new ThreadLocal<>(){
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat();
            }
        };

        SimpleDateFormat simpleDateFormat = (SimpleDateFormat)objectThreadLocal.get();

五、ThreadLocalMap

在这里插入图片描述
其实它的关键点都是 WeakReference ,弱引用对象,这里给大家普及下:强软弱虚

1、强引用:

比如 Object object=new Object(); new 创建出来的object 对象就是强引用。当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,是程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

2、软引用(SoftReference):

如果一个对象是软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

3、弱引用(WeakReference):

弱引用和软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内容区域的过程中,一旦发现了只具有弱引用对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

4、虚引用(PhantomReference):jvm和netty中使用,便于监控堆外内存

“虚引用”顾名思义,就是形同虚设的意思,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

六、ThreadLocal其他几个注意的点

在这里插入图片描述

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

七、面试相关问题解答

ThreadLocal是如何引起内存泄露呢(内存泄露:内存不使用没有及时被jvm回收,导致内存溢出)。其中它的Entry对象虽然被修饰为弱引用,但是还是会引发内存泄露。大家可以看上面的那张图理解,就是ThreadLocal设置为null后,虽然没有强引用了,但是此时的ThreadLocalMap对象是被当前线程持有的,所以不会回收。尤其是在线程池的使用过程中,线程是反复利用,不会释放。

在这里插入图片描述

写在最后,感谢点赞关注收藏转发

欢迎关注我的微信公众号 【猿之村】
在这里插入图片描述

来聊聊Java面试
加我的微信进一步交流和学习,微信手动搜索
【codeyuanzhicunup】添加即可
如有相关技术问题欢迎留言探讨,公众号主要用于技术分享,包括常见面试题剖析、以及源码解读、微服务框架、技术热点等。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页