面试必问-ThreadLocal实现原理-源码分析-面试分析

先看案例

结构分层

image.png

上代码

/**
 * 功能描述:ThreadLocal案例
 *
 * @author Songxianyang
 * @date 2022-05-29 12:50
 */

public class MyThreadLocal {
    public static void main(String[] args) {
        UserEntity userEntity = new UserEntity();
        userEntity.setId(1);
        userEntity.setName("songXY");
        userEntity.setType("VIP");
        new Service1().M(userEntity);
        System.out.println("以下是上来就直接初始化对象");
        new InitialValueService4().M();
    }
}

class Service1 {
    public void M(UserEntity entity) {
        ThreadLocalUser.userEntityThreadLocal.set(entity);
        System.out.println("通过set的方式往ThreadLocal放对象");
        System.out.println("---------------");
        new Service2().M();
        System.out.println("---------------");
        new Service3().M();
    }
}

class Service2 {
    public void M() {
        UserEntity userEntity = ThreadLocalUser.userEntityThreadLocal.get();
        System.out.println(userEntity.getId());
        System.out.println(userEntity.getName());
        System.out.println(userEntity.getType());
        
    }
}

class Service3 {
    public void M() {
        UserEntity userEntity = ThreadLocalUser.userEntityThreadLocal.get();
        System.out.println(userEntity.getId());
        System.out.println(userEntity.getName());
        System.out.println(userEntity.getType());
        // 用完之后记得 remove 掉防止OOM
        ThreadLocalUser.userEntityThreadLocal.remove();
    }
}

class ThreadLocalUser {
    public static ThreadLocal<UserEntity> userEntityThreadLocal = new ThreadLocal<>();
}

class InitialValue {
    public static ThreadLocal<UserEntity> userEntityThreadLocal =  ThreadLocal.withInitial(()->{
        UserEntity userEntity = new UserEntity();
        userEntity.setId(2);
        userEntity.setName("喜羊羊");
        userEntity.setType("SVIP");
        return userEntity;
    });
}

class InitialValueService4 {
    public void M() {
        UserEntity userEntity = InitialValue.userEntityThreadLocal.get();
        System.out.println(userEntity.getId());
        System.out.println(userEntity.getName());
        System.out.println(userEntity.getType());
        System.out.println("---------------");
        new InitialValueService5().M();
    }
}
class InitialValueService5 {
    public void M() {
        UserEntity userEntity = InitialValue.userEntityThreadLocal.get();
        System.out.println(userEntity.getId());
        System.out.println(userEntity.getName());
        System.out.println(userEntity.getType());
        
    }
}

运行结果

image.png

什么是 ThreadLocal

  • ThreadLocal 用的比较多的就是用来维护一个对象。让他在多线程访问下处于线程安全。来保证数据访问的正确性。多线程下不去共享同一个变量。一个线程只维护一个实例。线程若被销毁随着该线程所持有的对象也会被销毁。(销毁可以理解成回收) e.value = null;

我们来分析源码

首先:三个对象来来说明问题

image.png

ThreadLocalMap

这个类属于Thread的成员变量且ThreadLocalMap 内部维护了一个 Entry (也就相当于一个map)

Entry 与Map的区别

map在hash冲突的时候数组转链表转红黑树
Entry 在hash冲突时,在数组中找到空闲位置直接放在空闲问题。也就相当与用数组形式来维护ThreadLocalMap

ThreadLocalMap 和 Thread 内部关系

image.png
一个线程对一个ThreadLocalMap,map是个集合容器,可以存多个数据

ThreadLocalMap 和ThreadLocal 内部关系

  • 在 您 get(),set(),remove()的时候都不然去找 当前线程的ThreadLocalMap,等到下面看源码的时候,就知道了

分析get()源码 非常之简单

public T get() {

    // 找到当前线程
    Thread t = Thread.currentThread();
    // 从当前线程中找到 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // map 不等null
    if (map != null) {
    // ThreadLocalMap 和 Thread 内部关系 图
    // 再看 map.getEntry(this) 中的this 也就是当前的:ThreadLocal ,去找对应的Value值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            //  获取Value的值
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
private T setInitialValue() {
// 获取初始值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    // this 当前的ThreadLocal value:初始值
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

分析set()源码 非常之简单

public void set(T value) {
// 获取当前线程
    Thread t = Thread.currentThread();
    // 从当前线程中找到 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
    // this 当前的ThreadLocal value:你传过来的value
        map.set(this, value);
    else
        createMap(t, value);
}

分析remove源码

public void remove() {
// 从当前线程中找到 ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
    //移除对应的key
        m.remove(this);
}

内存泄露的源码分析

Entry 构造方法

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    // 首先K,WeakReference他来修饰的,弱引用
    // V:强应用 。也就是一个对象。
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

强引用 弱引用 区别

new 出来的对象都是 强引用
对象用完之后立即回收 弱引用

ThreadLocal把强引用设置为null 实现被GC回收细节

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        // 如果key 被回收后,key等于null
        if (k == null) {
        // 把value 也设置为null 让他被回收
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

如何防止内存泄露

  • ThreadLocal 用完之后,调用remove方法

ThreadLocal 在使用时 空指针异常如何解决

类型使用基本类型 报空指针
包装类型就不会报空指针,如果为null,后台则打印“null",从而不会报异常
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SteveCode.

永远年轻,永远热泪盈眶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值