ThreadLocal

为何引入ThreadLocal

ThreadLocal​对象可以提供线程局部变量,每个线程Thread​拥有一份自己的副本变量,多个线程互不干扰. 下面举例说明引入ThreadLocal的有优点.

SimpleDateFormat

private SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public void test(){
    String dateStr = f.format(new Date());
    // 业务流程
}

SimpleDateFormat存在线程安全问题. 测试如下:

public class Main {
    private static SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        while (true) {
            new Thread(() -> {
                String dateStr = f.format(new Date());
                System.out.println(new Date());
                System.out.println(dateStr);
                try {
                    Date parseDate = f.parse(dateStr);
                    String dateStrCheck = f.format(parseDate);
                    boolean equals = dateStr.equals(dateStrCheck);
                    if (!equals) {
                        System.out.println(equals + " " + dateStr + " " + dateStrCheck);
                    } else {
                        System.out.println(equals);
                    }
                } catch (ParseException e) {
                    System.out.println(e.getMessage());
                }
            }).start();
       }
    }
}

可以发现, 不仅存在格式化不一致的问题, 还存在输入错误问题. 因为SimpleDateFormat有个成员变量calendar​, 且会被修改.

要想解决SimpleDateFormat的问题, 可以每次使用时采用new重新创建对象, 但对于同一线程使用是安全的, 没必要每次新建. 引入ThreadLocal可解决该问题, 实测下面的例子都是equal的

public class Main {
    private static ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(
        () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static void main(String[] args) {
        while (true) {
            new Thread(() -> {
                SimpleDateFormat f = threadLocal.get();
                String dateStr = f.format(new Date());
                System.out.println(new Date());
                System.out.println(dateStr);
                try {
                    Date parseDate = f.parse(dateStr);
                    String dateStrCheck = f.format(parseDate);
                    boolean equals = dateStr.equals(dateStrCheck);
                    if (!equals) {
                        System.out.println(equals + " " + dateStr + " " + dateStrCheck);
                    } else {
                        System.out.println(equals);
                    }
                } catch (ParseException e) {
                    System.out.println(e.getMessage());
                }
            }).start();
        }
    }
}

源码分析

数据结构ThreadLocalMap

ThreadLocal内部使用了ThreadLocalMap, 底层采用数组实现, 既然是Map, 就需要计算哈希以及解决哈希冲突. 其中key为thread

哈希算法

public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();

    private static AtomicInteger nextHashCode = new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    static class 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);
        }
    }
}

其中i就是当前key在hash表数组中的下标位置, 每次新建一个ThreadLocal对象, nextHashCode会增加0x61c88647, 这个值属于斐波那契数 也叫 黄金分割数, 用于均匀散列数字.

 public static void main(String[] args) {
        int hashInc = 0x61c88647;
        for (int i = 0; i < 16; i++) {
            int hashCode = i *  hashInc;
            int idx = hashCode & 15;
            System.out.println("斐波那契散列:" + idx + " 普通散列:" + (String.valueOf(i).hashCode() & 15));
        }
 }
斐波那契散列:0 普通散列:0
斐波那契散列:7 普通散列:1
斐波那契散列:14 普通散列:2
斐波那契散列:5 普通散列:3
斐波那契散列:12 普通散列:4
斐波那契散列:3 普通散列:5
斐波那契散列:10 普通散列:6
斐波那契散列:1 普通散列:7
斐波那契散列:8 普通散列:8
斐波那契散列:15 普通散列:9
斐波那契散列:6 普通散列:15
斐波那契散列:13 普通散列:0
斐波那契散列:4 普通散列:1
斐波那契散列:11 普通散列:2
斐波那契散列:2 普通散列:3
斐波那契散列:9 普通散列:4

设置值

new ThreadLocal<>().set("aa");


private void set(ThreadLocal<?> key, Object value) {
    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)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            e.value = value;
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

  • 若k等于key: 说明找到了对应的ThreadLocal, 直接更新
  • 若key为null: 说明这个位置的 ThreadLocal​ 已经被垃圾回收,调用 replaceStaleEntry​ 方法替换这个过期的条目
  • 若没有对应的ThreadLocal: 新建item, 尝试清理过期元素, 若空间还是不够则扩容.

posted @   shmilyt  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示