• ThreadLocal原理和使用场景


    简介

    ThreadLocal是一个关于创建线程局部变量的类。

    通常情况下,我们创建的成员变量都是线程不安全的。因为他可能被多个线程同时修改,此变量对于多个线程之间彼此并不独立,是共享变量。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程无法访问和修改。也就是说:将线程公有化变成线程私有化(空间换时间)。

    核心源码分析

    注意:ThreadLocal通常都定义为static,ThreadLocal没有存储功能,变量副本的真实存储位置是Thread对象的threadLocals这个ThreadLocal.ThreadLocalMap变量中,可以将ThreadLocal理解为一个工具类,用来保证线程本地变量的存储和存储碰撞。

    1. public static void main(String[] args){
    2. ThreadLocal threadLocal = new ThreadLocal();
    3. threadLocal.set("haha");
    4. System.out.println(threadLocal.get());
    5. }

    ThreadLocal(为key)

    set

    创建一个和当前线程强相关的容器,把值放到这个容器里,线程之间容器相互隔离。

    1. /**
    2. * Sets the current thread's copy of this thread-local variable
    3. * to the specified value. Most subclasses will have no need to
    4. * override this method, relying solely on the {@link #initialValue}
    5. * method to set the values of thread-locals.
    6. *
    7. * @param value the value to be stored in the current thread's copy of
    8. * this thread-local.
    9. */
    10. public void set(T value) {
    11. Thread t = Thread.currentThread();
    12. ThreadLocalMap map = getMap(t);
    13. if (map != null)
    14. map.set(this, value);
    15. else
    16. createMap(t, value);
    17. }
    1. /**
    2. * Create the map associated with a ThreadLocal. Overridden in
    3. * InheritableThreadLocal.
    4. *
    5. * @param t the current thread
    6. * @param firstValue value for the initial entry of the map
    7. */
    8. void createMap(Thread t, T firstValue) {
    9. t.threadLocals = new ThreadLocalMap(this, firstValue);
    10. }

    此段代码可以看出,我们在set的时候,是获取了一个ThreadLocalMap对象(上述当中的容器),它是当前线程的一个参数,如果已经建立了,以threadlocal为key,直接往里面塞值,没有建立的话需要初始化一下。

    get

    1. /**
    2. * Returns the value in the current thread's copy of this
    3. * thread-local variable. If the variable has no value for the
    4. * current thread, it is first initialized to the value returned
    5. * by an invocation of the {@link #initialValue} method.
    6. *
    7. * @return the current thread's value of this thread-local
    8. */
    9. public T get() {
    10. Thread t = Thread.currentThread();
    11. ThreadLocalMap map = getMap(t);
    12. if (map != null) {
    13. ThreadLocalMap.Entry e = map.getEntry(this);
    14. if (e != null) {
    15. @SuppressWarnings("unchecked")
    16. T result = (T)e.value;
    17. return result;
    18. }
    19. }
    20. return setInitialValue();
    21. }
    1. /**
    2. * Variant of set() to establish initialValue. Used instead
    3. * of set() in case user has overridden the set() method.
    4. *
    5. * @return the initial value
    6. */
    7. private T setInitialValue() {
    8. T value = initialValue();
    9. Thread t = Thread.currentThread();
    10. ThreadLocalMap map = getMap(t);
    11. if (map != null)
    12. map.set(this, value);
    13. else
    14. createMap(t, value);
    15. return value;
    16. }

    获取线程中ThreadLocalMap已经存储的内容,如果ThreadLocalMap还没创建,则创建map并赋null。

    remove

    1. /**
    2. * Removes the current thread's value for this thread-local
    3. * variable. If this thread-local variable is subsequently
    4. * {@linkplain #get read} by the current thread, its value will be
    5. * reinitialized by invoking its {@link #initialValue} method,
    6. * unless its value is {@linkplain #set set} by the current thread
    7. * in the interim. This may result in multiple invocations of the
    8. * {@code initialValue} method in the current thread.
    9. *
    10. * @since 1.5
    11. */
    12. public void remove() {
    13. ThreadLocalMap m = getMap(Thread.currentThread());
    14. if (m != null)
    15. m.remove(this);
    16. }
    1. /**
    2. * Remove the entry for key.
    3. */
    4. private void remove(ThreadLocal key) {
    5. Entry[] tab = table;
    6. int len = tab.length;
    7. int i = key.threadLocalHashCode & (len-1);
    8. for (Entry e = tab[i];
    9. e != null;
    10. e = tab[i = nextIndex(i, len)]) {
    11. if (e.get() == key) {
    12. e.clear();
    13. expungeStaleEntry(i);
    14. return;
    15. }
    16. }
    17. }

    remove就是将存储的变量清空。

    ThreadlocalMap

    set

    1. /**
    2. * Set the value associated with key.
    3. *
    4. * @param key the thread local object
    5. * @param value the value to be set
    6. */
    7. private void set(ThreadLocal key, Object value) {
    8. // We don't use a fast path as with get() because it is at
    9. // least as common to use set() to create new entries as
    10. // it is to replace existing ones, in which case, a fast
    11. // path would fail more often than not.
    12. Entry[] tab = table;
    13. int len = tab.length;
    14. int i = key.threadLocalHashCode & (len-1);
    15. for (Entry e = tab[i];
    16. e != null;
    17. e = tab[i = nextIndex(i, len)]) {
    18. ThreadLocal k = e.get();
    19. if (k == key) {
    20. e.value = value;
    21. return;
    22. }
    23. if (k == null) {
    24. replaceStaleEntry(key, value, i);
    25. return;
    26. }
    27. }
    28. tab[i] = new Entry(key, value);
    29. int sz = ++size;
    30. if (!cleanSomeSlots(i, sz) && sz >= threshold)
    31. rehash();
    32. }

    先用hash码与长度取与,然后找到对应数组位置index,如果没有发生碰撞,就按指定位置存储,如果发生碰撞,就顺序找个空的位置存储。

    get

    1. /**
    2. * Get the entry associated with key. This method
    3. * itself handles only the fast path: a direct hit of existing
    4. * key. It otherwise relays to getEntryAfterMiss. This is
    5. * designed to maximize performance for direct hits, in part
    6. * by making this method readily inlinable.
    7. *
    8. * @param key the thread local object
    9. * @return the entry associated with key, or null if no such
    10. */
    11. private Entry getEntry(ThreadLocal key) {
    12. int i = key.threadLocalHashCode & (table.length - 1);
    13. Entry e = table[i];
    14. if (e != null && e.get() == key)
    15. return e;
    16. else
    17. return getEntryAfterMiss(key, i, e);
    18. }
    19. /**
    20. * Version of getEntry method for use when key is not found in
    21. * its direct hash slot.
    22. *
    23. * @param key the thread local object
    24. * @param i the table index for key's hash code
    25. * @param e the entry at table[i]
    26. * @return the entry associated with key, or null if no such
    27. */
    28. private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    29. Entry[] tab = table;
    30. int len = tab.length;
    31. while (e != null) {
    32. ThreadLocal k = e.get();
    33. if (k == key)
    34. return e;
    35. if (k == null)
    36. expungeStaleEntry(i);
    37. else
    38. i = nextIndex(i, len);
    39. e = tab[i];
    40. }
    41. return null;
    42. }

    查找和放入是一样的道理。

    remove

    1. /**
    2. * Remove the entry for key.
    3. */
    4. private void remove(ThreadLocal key) {
    5. Entry[] tab = table;
    6. int len = tab.length;
    7. int i = key.threadLocalHashCode & (len-1);
    8. for (Entry e = tab[i];
    9. e != null;
    10. e = tab[i = nextIndex(i, len)]) {
    11. if (e.get() == key) {
    12. e.clear();
    13. expungeStaleEntry(i);
    14. return;
    15. }
    16. }
    17. }

    看完代码,我们总结一下ThreadLocal它源码的核心几个方法及其作用

    • set()方法用于保存当前线程的副本变量值。
    • get()方法用于获取当前线程的副本变量值。
    • initialValue()为当前线程初始副本变量值。
    • remove()方法移除当前线程的副本变量值。

    抽象图示

    ThreadLocal弱引用

    To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
    为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为 key。

    通常ThreadLocalMap的生命周期跟Thread(注意线程池中的Thread)一样长,如果没有手动删除对应key(线程使用结束归还给线程池了,其中的KV不再被使用但又不会GC回收,可认为是内存泄漏),一定会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal会被GC回收,不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,Java8已经做了上面的代码优化。

    使用场景

    1.线程安全的时间工具类

    1. package com.test;
    2. import java.text.DateFormat;
    3. import java.text.ParseException;
    4. import java.text.SimpleDateFormat;
    5. import java.util.Date;
    6. public class DateUtil {
    7. private static final String date_format = "yyyy-MM-dd HH:mm:ss";
    8. private static ThreadLocal threadLocal = new ThreadLocal();
    9. public static DateFormat getDateFormat(String dateFormate) {
    10. DateFormat df = threadLocal.get();
    11. if(df == null){
    12. df = new SimpleDateFormat(dateFormate);
    13. threadLocal.set(df);
    14. }
    15. return df;
    16. }
    17. public static String formatDate(Date date,String dateFormate) throws ParseException {
    18. return getDateFormat(dateFormate).format(date);
    19. }
    20. public static Date parse(String strDate,String dateFormate) throws ParseException {
    21. return getDateFormat(dateFormate).parse(strDate);
    22. }
    23. }

    2.B端存储用户信息

    1. public class UserThreadLocal {
    2. private UserThreadLocal(){}
    3. private static final ThreadLocal LOCAL = new ThreadLocal<>();
    4. public static void put(SysUser sysUser){
    5. LOCAL.set(sysUser);
    6. }
    7. public static SysUser get(){
    8. return LOCAL.get();
    9. }
    10. public static void remove(){
    11. LOCAL.remove();
    12. }
    13. }

  • 相关阅读:
    Python QGIS 3自动化教程
    【Flink实战】用户统计:按照省份维度统计新老用户
    Scratch、Python、C++,谁才是少儿编程的第一选择?
    做个男人,做个成熟的男人
    Java集合List去重的几种方式
    【NOI模拟赛】汁树(树形DP)
    `Promise`全面解析
    Hive大数据项目环境搭建:安装部署Hive(超详细)
    阿里架构师:对于 Kafka 的消费者客户端详解,你都明白吗?
    【机器学习、python】线性回归
  • 原文地址:https://blog.csdn.net/gejiangbo222/article/details/128008739