栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ThreadLocal的使用及原理(源码解析)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ThreadLocal的使用及原理(源码解析)

1、ThreadLocal介绍 ThreadLocal能做什么?

提供线程内的局部变量,不同线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度

总结
1. 线程并发:在多线程并发的场景下
2. 传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量,类似于JavaWeb中的域对象
3. 线程隔离:每个线程的变量都是独立的,不会互相影响
ThreadLocal的基本使用

常用方法

方法声明描述
ThreadLocal()创建ThreadLocal对象
public void set(T value)设置当前线程绑定的局部变量
public T get()获取当前线程绑定的局部变量
public void remove()移除当前线程绑定的局部变量

使用案例

public class ThreadLocalDemo {
    // 设置一个ThreadLocal对象
    static ThreadLocal tl = new ThreadLocal<>();

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(()->{
                // 往ThreadLocal中添加数据
                tl.set(Thread.currentThread().getName());
                // 取出ThreadLocal中的数据
                System.out.println("线程+"+ finalI +"的名字是:"+tl.get());
            }).start();
        }
    }
}
ThreadLocal类与synchronized关键字的区别

虽然ThreadLocal和synchronized都能处理多线程并发访问变量的问题,但二者的角度和思路都不同

synchronizedThreadLocal
原理同步机制采用”以时间换空间“的方式,只提供了一份变量,让不同的线程排队访问ThreadLocal采用”以空间换时间“的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而互不干扰
侧重点多个线程之间访问资源的同步多线程中让每个线程之间的数据相互隔离
2、扩展——Java中的引用类型、特点、应用场景 强引用

代码:Object o = new Object();

特点:垃圾回收时不会回收强引用的对象

应用场景:最普遍的引用

软引用

代码:SoftReference m = new SoftReference<>(new byte[1024*1024*10]);

特点:垃圾回收时,如果内存不足,则才会回收软引用的对象

原理:m -->(强) SR -->(软) byte[]

应用场景:非常适合做缓存

弱引用

代码:WeakReference m = new WeakReference<>(new M());

特点:只要发生垃圾回收,就会回收弱引用的对象

原理:m -->(强) WR -->(弱) byte[]

应用场景:防止内存泄漏

在ThreadLocal中的使用

图中,key对ThreadLocal就是一个弱引用

虚引用

代码:PhantomReference phantomReference = new PhantomReference<>(new M(), QUEUE);

特点:几乎等同于没有引用,只有在回收时会给出一个信号,在GC的时候会用到

应用场景:JVM中对直接内存的管理

3、ThreadLocal的内部原理

前面讲了这么多废话,终于到了原理解析

ThreadLocal的结构

每个Thread中,都维护一个TheadLocalMap,就是一个map结构,而每个TheadLocalMap存放的key-value就是是ThreadLocal-value

这样设计的好处是

  1. 每个Map存储的Entry数量变少
  2. 当Thread销毁时,ThreadLocalMap也会随之销毁,减少了内存的使用
ThreadLocal的应用
  • Spring中@transactional的处理
  • MyBatis中分页的处理
ThreadLocal的set源码
ThreadLocal tl = new ThreadLocal<>();
tl.set(new Person());
// 以tl为key,Person为value装在了一个map里
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的一个map,这个map叫ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 往这个map里添加key-value,key是当前的ThreadLocal,value是传入set()方法里的value
        map.set(this, value);
    else
        createMap(t, value);
}

// 每new出一个thread对象,都会有一个成员变量threadLocals,get()方法就是用来返回这个threadLocals
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocal的set()的原理,在当前线程的TheadLocalMap里存入调用set()方法的ThreadLocal对象-key,传入set()方法的对象-value

ThreadLocal中map.set()源码
private void set(ThreadLocal key, Object value) {
    // Entry数组就是一个key-value对,就是map一条一条的记录
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
	
    // 整个for就是查找遍历存放Entry的数组table,检查key值是否已存在与table中,或key是否为null
    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) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
	
    // 将Entry数组放到table中
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

// Entry中的key是一个弱引用的子类
// WR -->(弱) ThreadLocal对象
static class Entry extends WeakReference> {
    
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}
ThreadLocal产生内存泄漏的原因

依然围绕着上图来看,假如我们不用ThreadLocal了,可以将其置为null,但是,线程中的ThreadLocalMap中的key会一直指向ThreadLocal,直到线程结束才会回收,所以,就会产生key的内存泄漏

并且,就算key的值变为null,但整个Entry依然存在,也会产生内存value的内存泄漏,因为key为null之后,value再也无法被访问到,但此时value依然强引用某个对象,所以无法被回收

  • 解决key的内存泄漏,ThreadLocal已经帮我们做了,那就是把key的引用设置为弱引用,这样,只要tl不再引用ThreadLocal对象,发生垃圾回收时,key就会被回收掉
  • 解决value的内存泄漏,每次get()或set()方法都会检查key是否为null,如果为null,则remove掉整个Entry数组,但依然存在问题,比如长时间不调用get()或set()方法,所以要养成手动remove的好习惯
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/287265.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号