ThreadLocal的理解使用,在SimpleDateForma的应用

0. ThreadLocal的使用:

如果开发者希望将类的某个静态变量与线程状态关联,则可以考虑使用ThreadLocal

在多线程中如果使用同一个SimpleDateFormat,并使用其parse方法时,那么会有线程安全问题

  • 原因是,SimpleDateForma内部有一个Calendar对象,调用parse()方法时,先Calendar.clear(),再Calendar.add()
  • 如果线程先调用了add(),然另一个线程调用了clear(),就会出现线程安全问题。

关于SimpleDateFormat安全的时间格式化线程安全问题可以参考链接

【举个栗子】:

public class ConcurrentDateUtil {
//    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static Date parse(String dateStr) throws ParseException {
        SimpleDateFormat simpleDateFormat = threadLocal.get();
        System.out.println(Thread.currentThread().getName() + "获取结果:" + simpleDateFormat);
        Date parse = null;
        try {
            parse = simpleDateFormat.parse(dateStr);
            System.out.println(Thread.currentThread().getName() + "解析结果:" + parse);
        } catch (ParseException e) {
            e.printStackTrace();
        } finally {
            threadLocal.remove();
        }
        return parse;
    }

    public static void main(String[] args) {
        String dataStr = "2020-10-15 22:02:20";
        new Thread(() -> {
            try {
                ConcurrentDateUtil.parse(dataStr);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }, "线程1").start();
        new Thread(() -> {
            try {
                ConcurrentDateUtil.parse(dataStr);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }, "线程2").start();
        new Thread(() -> {
            try {
                ConcurrentDateUtil.parse(dataStr);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }, "线程3").start();
    }

【输出结果】:
在这里插入图片描述

结果可以看出,运行结果正常,不会出现报错。

1. ThreadLocal的原理总结:
    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();
    }
    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;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • ConcurrentDateUtil的类对象threadLocal重写initialValue方法,为了在创建线程内部对象ThreadLocalMap时,初始化对应的value,也就是new ThreadLocalMap(threadlocal, simpleDateFormat);
  • 当threadLocal.get()时,实际先获取当前的线程Thread t ,然后再获取t的内部ThreadLocalMap对象
     如果获取的ThreadLocal.ThreadLocalMap为空,就新建该map
     取值的时候根据当前的threadLocal对象从map获取保存的值
  • 其实就是每个线程内部维护一个ThreadLocalMap map对象(并不是同一个map),来保存值,map的key是threadLocal,value为储存的值
    相当于:
     map1.set(threadLocal,object)
     map2.set(threadLocal,object)
  • 虽然多线程调用的是同一个threadLocal,但线程不同,内部的map也不同,就能保证各自保存的object不被别的线程影响

【为什么上面打印的simpleDateFormat一致】:

  • 明明三个线程都是new simpleDateFormat(),为什么打印是同一个?
  • 因为simpleDateFormat重写了hashCode方法,对于同一个pattern,hashCode是一样的,所以toString()结果是一样的,所以打印输出结果一致

在这里插入图片描述

2. ThreadLocal的内存溢出:
    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
  • ThreadLocalMap使用ThreadLocal的弱引用(WeakReference)作为Entry的key,在系统GC垃圾回收时,ThreadLocal就有可能被回收,
    这样ThreadLocalMap中就会出现key为null的Entry,这样就没法访问这是为null的value,就会造成内存泄露

所以我们要在使用完threadLocal后,对其进行remove,如上述栗子。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值