博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ThreadLocal使用方式,源码分析,内存泄漏
阅读量:4148 次
发布时间:2019-05-25

本文共 5097 字,大约阅读时间需要 16 分钟。

ThreadLocal是干什么的?

    用来隔离线程之间参数对象的,简单来说就是每个线程在使用的时候,都获取到每个线程上所绑定参数,举个例子,web开发项目当中,每个用户登录进来,访问网站,都会有个新线程创建,我们在每个线程中绑定用户信息,这样就可以在任何时刻获取到当前用户的信息,避免了参数传递。

先来看一个简单的例子

public class ThreadLocalTest1 {    ThreadLocal
longLocal = ThreadLocal.withInitial(()-> Thread.currentThread().getId() ); ThreadLocal
stringLocal = ThreadLocal.withInitial(() -> Thread.currentThread().getName()); public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws Exception{ final ThreadLocalTest1 test = new ThreadLocalTest1(); // test.set(); 如果没有重写initValue方法会导致抛出异常 System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { // test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); }}

执行结果:

1main11Thread-01main

我们在每个线程当中都保存了各自的线程id和线程名称,

ThreadLocal 当中get和set方法的实现

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

1、获取到当前线程

2、从当前线程中查询到线程内部的变量ThreadLocalMap

3、如果当前线程中维护的ThreadLocalMap不为null , 查询map中,key为ThreadLocal的value值

4、如果当前线程ThreadLocalMap还没初始化,执行setInitialValue

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

1、获取到ThreadLocal中initialValue值

2、判断当前线程中是否包含ThreadLocalMap,

3、如果当前线程中含有ThreadLocalMap, 放入进去当前ThreadLocal, 和ThreadLocal中initialValue值,

4、如果没有ThreadLocalMap, 创建一个Map,

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

这段代码很简单就是创建一个新的ThreadLocalMap,将当前ThreadLocal及ThreadLocal 中initialValue的值传递进去

总结起来就是:

每个线程都含有自己的ThreadLocalMap<ThreadLocal,Object>, Obejct的值,就是我们需要为每个线程所保存的一个数据,如果没有重写ThreadLocal当中的initialValue方法,需要调用ThreadLocal的set方法将数据保存进去,否则会抛出空指针异常,如果重写了initialValue方法,在get时,会将initialValue保存进去

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

1、获取到当前线程中保存的ThreadLocalMap,

2、如果ThreadLocalMap不为空,将当前ThreadLocal和value放置进去

3、如果为null, 创建一个新的ThreadLocalMap

如果ThreadLocal当中value是一个共总对象,那么每个线程之间,关于这个对象的保存并不是隔离开的

如下例子:

public class ThreaLocalTest2 {    private static A a  = new A();    private static final ThreadLocal threadLocal = ThreadLocal.withInitial(()->a);    public static void main(String[] args) {        Thread[] threads = new Thread[5];        for (int i = 0; i <  threads.length; i++) {            threads[i] = new Thread(() ->{                threadLocal.get().setNumber(threadLocal.get().getNumber() + 5);                System.out.println(Thread.currentThread().getName() + ":"                 + threadLocal.get().getNumber());            },"Thread-"+i);        }        for(Thread thread : threads) {            thread.start();        }    }}class A{    private int number = 0;    public int getNumber() {        return number;    }    public void setNumber(int number) {        this.number = number;    }}

执行结果:

Thread-0:5Thread-2:15Thread-1:10Thread-4:20Thread-3:25

这个结果显然并不是我们想要的,原因就在于他们访问的是同一个对象,如果将ThreadLocal.withInitial(() ->new A()),就可以保证每个线程访问的数据之间是隔离开来的

ThreadLocal内存泄漏

    先来看下ThreadLocal当中ThreadLocalMap的类

static class ThreadLocalMap { static class Entry extends WeakReference
> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal
k, Object v) { super(k); value = v; } }}

ThreadLocalMap当中entry维护了ThreadLocal的弱引用,其内存管理

关于强引用 A a = new A(); B b = new B();

C c = new C(b);

如果b = null , 那么在垃圾回收时,并不会将b回收走,原因是在c 内部维护了b的强引用,如果c = null ,那么b 是会被回收走的,还有一种情况就是 WeakReference weak = new WeakReference(b),c的内部使用weak, 这样在b = null 时,b就会被垃圾回收收走。

在ThreadLocalMap当中维护了ThreaLocal的弱引用,当threadLocal为null 时,就可以被回收走,但是ThreadLocalMap还维护了value, 而value是强引用,只有等当前线程消亡了之后,才会被垃圾回收掉,但是如果是在线程池中,线程一直被重复使用,value将永远不会被回收,就会发生内存泄漏,

但是,ThreadLocal中的ThreaLocalMap在setEntry和getEntry,会对key进行判断,如果ThreadLocal为null, 也会将value置为null

但是如果分配了内存之后,并没有调用ThreadLocal中的get, set 或者remove方法,那么key为null的,value值将不会被垃圾回收掉,建议在使用ThreadLocal时,即使进行清理,防止内存泄漏,尤其是在线程池当中使用ThreadLocal.

参考:

          

          

        

你可能感兴趣的文章
android路由表,我了解到的面试的一些小内幕!附超全教程文档
查看>>
android路由跳转,Android面试资料集合,完整PDF
查看>>
android进程共享,记一次字节跳动Android社招面试,全网最新
查看>>
Android进程管理,有了这些中高端面试专题-大厂还会远吗?使用指南
查看>>
android适配屏幕大小,Android-MVP模式详解,全网疯传
查看>>
Android面试中常问的MMAP到底是啥东东?终局之战
查看>>
Android开发者跳槽面试,积累总结
查看>>
Android开发者面试如何系统复习?帮你突破瓶颈
查看>>
android开发面试题,分享一些行业经验,写给正在求职的安卓开发
查看>>
Android最强保活黑科技的最强技术实现,满满干货指导
查看>>
Android面试吃透这一篇就没有拿不到的offer!大牛最佳总结
查看>>
Android性能优化之启动优化实战篇!系列篇
查看>>
Android推送技术解析,不吃透都对不起自己
查看>>
Android插件化主流框架和实现原理,全网独家首发!
查看>>
android插件化面试,我的头条面试经历分享,绝对干货
查看>>
Android架构师必备框架技能核心笔记,面试心得体会
查看>>
字节面试官:记录下我磕磕碰碰的三个月找工作经历,含BATJM大厂
查看>>
安卓app启动速度优化,Android性能优化之启动优化实战篇,系列篇
查看>>
安卓framework,十年开发经验Android架构师,重难点整理
查看>>
安卓framework,面试大厂应该注意哪些问题?隔壁都馋哭了
查看>>