ThreadLocal

ThreadLocal为每隔使用该变量的线程提供独立的变量副本。

并发环境下 我们使用simpledataformat。我们可以封装一下用threadlocal来保存。

为什么这么做呢?

  1. simpledateformat是不安全的
  2. simpledateformate中的calendar线程是不安全的
  3. calendar是线程不安全,为什么不安全?
  4. 类中存放日期数据变量是线程不安全的,比如里面的time等属性
public class TestSimpleDateFormat2 {
    // (1)创建threadlocal实例
    static ThreadLocal<DateFormat> safeSdf = new ThreadLocal<DateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static void main(String[] args) {
        // (2)创建多个线程,并启动
        for (int i = 0; i < 10; ++i) {
            Thread thread = new Thread(() -> {
             // (3)使用单例日期实例解析文本
                try {
                    System.out.println(safeSdf.get().parse("2017-12-13 15:17:27"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }

            });
            thread.start();// (4)启动线程
        }
    }
}

ThreadLocal源码分析

public void set(T value) {
    Thread t = Thread.currentThread();
    //1
    ThreadLocalMap map = getMap(t);
    if (map != null)
       //key ThreadLocal本身,value:线程的变量副本(value)
        map.set(this, value);
    else
        createMap(t, value);
}
//1.1
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
//1.2 thread中引用的threadLocalmap变量
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}

//没有则创建map
 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal只是在set的时候去操作了thread内部的ThreadLocalMap
Key就是当前的ThreadLocal,Value就是变量的副本

get方法:

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

如何创建ThreadLocalMap

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

1.创建的时候 会生成一个entry数组
2.通过与运算获取table下标数组i
3. 每个table对应一个entry数组,也就是 数组+entry(key,value)结构

看一下如何创建entry?
   static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

什么是强引用?等号(=)关系,我们认为是强引用。
什么是弱引用?使用WeakReference
上面的entry就是 使用弱引用。

但是 请注意
继承 WeakReference,将ThreadLocal<?> k,作为弱引用,回收的是k,不是entry,不是value。
如果回收的是k,那么变为null,value还是强引用。

顺便提一下如何找到回收的对象?(那些对象能回收,哪些对象不能回收)
  1. 引用计数法:jvm不会用了。
  2. 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
哪些对象可以被看做是 GCRoots 呢?

什么是GCROOTS:从根节点向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中的类静态属性引用的对象,常量引用的对象;
  3. 本地方法栈中 JNI(Native 方法)引用的对象;
梳理一下ThreadLocal到底长什么样子?

threadlocal内部有一个静态类, threadLocalmap,用来存储键值对
每次set线程的时候,先获取一下当前线程,然后看有没有localmap
如果没有,则创建map
如果有,则将 threadLocal作为key,值作为value保存在 treadLocalMap的一个entry数组中。
并且这个entry数组是使用弱引用的,需要注意的是,弱引用仅仅是引用的key,那么就会出现一个问题,如果回收了key,key变为了null,那么value就会在内存中不会被回收。

重新思考为什么这么做

为什么thread持有ThreadLocal中的threadLocalMap?在这里插入图片描述

从几方面来考虑:

  1. threadLocal仅仅是作为一个工具类,那么在设计的时候就不应该持有线程的任何数据,线程的任何数据在thread里面。
为什么entry 的key为弱引用?

假设下面一段代码:

ThreadLocal<M> tl = new TheadLocal<>(); //tl强引用threadlocal
tl.set(new M());//
tl.remove()

在这里插入图片描述

在这里插入图片描述

Thread线程 长期存在,tl长期存在,map就会长期存在,那么key也就会长期存在。

当线程持有的threadLocal 释放的时候,threadLocal由于持有threadLocalMap的弱引用,threadLocalMap即使没有手动释放,都会回收它。
但是需要 注意的是key 为null的时候,导致value无法回收内存。
所以threadLocalMap调用 set(),get(),remove()方法的时候会判断并且清空value的值。
我们在写代码的时候,需要添加finnally代码块,并且调用remove()方法,来清除key为null的情况。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值