多线程:ThreadLocal探究之旅

ThreadLocal概述

  1. ThreadLocal提供线程一个独立的局部变量,解决了变量并发访问的冲突问题。
  2. 解决了线程安全的问题
  3. ThreadLocal 对比给Thread上synchronized同步机制:前者空间换时间、后者时间换空间

Threadlocal主题思维结构图

在这里插入图片描述
核心部分会单独拿出来画图、分析!

Threadlocal使用


@RequiresApi(api = Build.VERSION_CODES.O)
public class ThreadLocalTest {
    static ThreadLocal<String> local = ThreadLocal.withInitial(() -> {
        return "默认初始值";
    });
    static ThreadLocal local1 = new ThreadLocal();


    public static void main(String[] args) {
        System.out.print("local = " + local.get()+'\n');
        local.set("this Local");
        local1.set("this Local1111");
//        local.remove();
        System.out.print("Local1 = "+local1.get() + "     set Local = " + local.get());
    }

}

Log:
在这里插入图片描述

核心函数(对外 public/protected)

initialValue

当前线程初始副本变量值,一般由子类继承ThreadLocal重写,return一个默认值

get:返回此线程局部变量在当前线程副本中的值
  public T get() {
        Thread t = Thread.currentThread();
        //1.根据当前线程调用getMap()获取Thread内部的ThreadLocal.ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //2 map不为null,从map中获取当前ThreadLocal的存储实体对象
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 3 map为null,调用setInitialValue
        // 3.1 map不存在,此线程中没用维护的ThreadLocalMap对象
        // 3.2 map存在,但是没有与ThreadLocal关联的ThreadLocalMap.Entry
        return setInitialValue();
    }

    private T setInitialValue() {
        //1. 获取初始化线程变量副本
        // 子类重写 initialValue
        T value = initialValue();
        //2.根据当前线程调用getMap()获取Thread内部的ThreadLocal.ThreadLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //2.1 存在存入map
        if (map != null)
            map.set(this, value);
        // 2.2 不存在创建map,并存入
        else
            createMap(t, value);
        return value;
    }
set
public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的TreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
        // 调用ThreadLocalMap的内部方法set(set后面会ThreadLocalMap分析中讲解)
            map.set(this, value);
        // 不存在创建map,并存入
        else
            createMap(t, value);
    }

// 创建与ThreadLocal关联的ThreadLocalMap
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • remove:移除当前前程的副本变量值

通过getMap获取当前线程的ThreadLoaclMap,不为null调用ThreadLoaclMap内部的remove方法

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreadLocalMap

在这里插入图片描述

  • set
private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
			// 这里是为了解决Hash冲突问题
            for (Entry e = tab[i];//获取Entry对象
                 e != null;// 不为null
                 e = tab[i = nextIndex(i, len)]) {
                 //i = nextIndex(i, len) 得到一个key值的元素的位置
                ThreadLocal<?> k = e.get();
				// 如果是同一个对象,最新的value会覆盖掉旧value
                if (k == key) {
                    e.value = value;
                    return;
                }
				// 如果key为null,则替换它的位置
                if (k == null) {
                    // 替换掉旧值
                    replaceStaleEntry(key, value, i);
                    // prevIndex(int i, int len)在此方法中调用
                    return;
                }
                 // 否则往后一个位置找,直到找到空的位置
            }
			// 上面的for循环主要作用:出现哈希冲突时,1:看是否是同一个对象或者是是否可替换,
			//                                  2:否则往后移动一位,继续判断
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
  • remove
  /**
         * Remove the entry for key.
         */
        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;
                }
            }
        }

总结

Thread内部结构:
在这里插入图片描述

组成
  1. 每个Thread内部有一个Map(ThreadLocalMap)
  2. Map里存储(Key)ThreadLocal,和(value)线程变量副本
  3. Thread内部的map由ThreadLocal维护,由ThreadLocal负责向map 设置/获取线程变量值
  4. 形成线程隔离
Entry
 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

  1. 存储的key(ThreadLocal),为弱引用
  2. 设计弱引用将对象生命周期和生命周期解绑

问题----------->

  1. 设计好处?
    节省内存的使用,Thread销毁,内部的map也随之销毁

  2. 和synchronized比较?
    synchronized: 时间换空间
    ThreadLocal:空间换时间

  3. ThreadLocal内存泄漏的真实原因?
    ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除key,就会造成内存泄漏。在使用完ThreadLocal,调用remove删除Entry

  4. 为什么使用弱引用?
    如果使用强引用,当ThreadLocal 对象的引用(强引用)被回收了,ThreadLocalMap本身依然还持有ThreadLocal的强引用,如果没有手动删除这个key ,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收, 可以认为这导致Entry内存泄漏。
    在使用完ThreadLocal后忘记调用remove,使用弱引用多一层保障:弱引用的ThradLocal被回收时,对应的value在下一次ThrealLocal调用get、set、remove时,旧元素会被清除,从而避免内存泄漏

  5. hashCode冲突?

// 是一个提供原子操作的Integer类,通过线程安全的方式操作加减
private static AtomicInteger nextHashCode =
        new AtomicInteger();
// HASH_INCREMENT = 0x61c88647 (16进制)目的哈希码均匀的分布在2的n次方的entry数组里减少hash冲突
 private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
// 减少hash冲突的次数
 key.threadLocalHashCode & (len - 1);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值