首页 > 其他分享 >ThreadLocal

ThreadLocal

时间:2024-10-20 20:23:46浏览次数:7  
标签:契散列 斐波 ThreadLocal println new 散列

为何引入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, 尝试清理过期元素, 若空间还是不够则扩容.

标签:契散列,斐波,ThreadLocal,println,new,散列
From: https://www.cnblogs.com/shmilyt/p/18487801

相关文章

  • ThreadLocal源码详解
    目录Thread、ThreadLocalMap、ThreadLocal关系ThreadLocal中的get、Set方法ThreadLocal内存泄露问题Thread、ThreadLocalMap、ThreadLocal关系从源码可以看出:Thread类中有成员变量ThreadLocalMap,ThreadLocalMap类中有成员变量Entry[]数组,每个Entry对象的key是Thread......
  • ThreadLocal内存泄漏怎么回事
    ThreadLocal本地线程,调用set方法往里面存的值,是每个线程互相隔离,互不影响的,每个线程都有一块存储ThreadLocal数据的地方叫做ThreadLocalMap,这个变量专门用于存储当前线程的map数据,调用ThreadLocal.set方法的时候,就是往这个ThreadLocalMap里面存储一个一个的entry,由key和value组成......
  • ThreadLocal和连接池
    ThreadLocal线程隔离工具用来存储一些只有线程才可以访问的内容。你可能会想,既然我只想本线程才能访问,那么我使用局部变量不就行了吗?局部变量的问题在于它只能存在于本方法内部,没有办法让本线程内的其他方法访问使用。publicstaticvoidmain(String[]args){Run......
  • 2024.9.26 ThreadLocal
    在使用ThreadLocal的情况下,并发量很高时不会产生冲突,原因如下:1.线程隔离:ThreadLocal为每个线程提供独立的存储空间。每个线程都可以安全地设置和获取其自己的变量值,而不会影响其他线程。即使在高并发环境下,线程间的数据是隔离的。2.并发安全:ThreadLocal本身是线程安......
  • Thread , ThreadLocal , ThreadLocalMap , Entry 之间的关系?
    Thread,ThreadLocal,ThreadLocalMap,Entry之间的关系?首先ThradLocal是线程的本地副本,怎么理解这句话呢?一个Thread都有一个它自己的ThreadLocalMap。ThreadLocalMap不是HashMap的结构,而是一个Entry数组,里面存放了一个一个的Entry。而Entry中存放的就是key和value,这个......
  • 【JUC并发编程系列】深入理解Java并发机制:线程局部变量的奥秘与最佳实践(五、ThreadLo
    文章目录【JUC并发编程系列】深入理解Java并发机制:线程局部变量的奥秘与最佳实践(五、ThreadLocal原理、对象之间的引用)1.基本API介绍2.简单用法3.应用场景4.Threadlocal与Synchronized区别5.内存溢出和内存泄漏5.2内存溢出(MemoryOverflow)5.2内存泄漏(Mem......
  • 《深入解析 Java 中的 ThreadLocal》
    ThreadLocal1.概述ThreadLocal被称为线程局部变量,用于在线程中保存数据。由于在ThreadLocal中保存的数据仅属于当前线程,所以该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal用于在同一个线程间,在不同的类和方法之间共享数据的的场景,也可以......
  • 【Java】深入解析ThreadLocal——Java并发编程的秘密武器
            ThreadLocal被称为线程局部变量,用于在线程中保存数据。由于在ThreadLocal中保存的数据仅属于当前线程,所以该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。        ThreadLocal用于在同一个线程间,在不同的类和方法之间共享数据......
  • 【Java基础】ThreadLocal<LoginUser>:存储登录用户信息
    ......
  • 并发编程 (TransmittableThreadLocal)
    TransmittableThreadLocal的使用及原理解析一、基本使用首先,TTL是用来解决ITL解决不了的问题而诞生的,所以TTL一定是支持父线程的本地变量传递给子线程这种基本操作的,ITL也可以做到,但是前面有讲过,ITL在线程池的模式下,就没办法再正确传递了,所以TTL做出的改进就是即便是在线程池模......