ThreadLocal详解

发布于:2025-03-03 ⋅ 阅读:(16) ⋅ 点赞:(0)

定义与用途

定义

ThreadLocal是java提供的线程私有局部变量的工具。以ThreadLocal为键,任意类型为值,每个线程可以持有自己的变量。

用途

可以在多线程环境中使用,为每个线程提供私有的变量。主要用于存储用户id,traceId等。不需要在方法中传递,可以作为线程的私有变量。

主要方法

T initialValue()

设置初始值的方法,当get的时候没有值的时候,会调用此方法来初始化值

    protected T initialValue() {
        return null;
    }

需要每个实现类自己去继承

void remove()

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

移除该局部变量的值

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

void set(T value)

为当前线程设置局部变量

  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

底层原理

  1. 每个Thread类会包含一个ThreadLocalMap,用来存储当前线程的所有的局部变量
ThreadLocal.ThreadLocalMap threadLocals = null;
  1. 当调用ThreadLocal.set(T value)的时候,会对当前线程的ThreadLocalMap进行赋值。
  public void set(T value) {
  		// 获取当前线程
        Thread t = Thread.currentThread();
        // 主要关注下这个getMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// 如果map不为空,则给map赋值
            map.set(this, value);
        } else {
        	// 创建map并且给赋值
            createMap(t, value);
        }
    }

	// 返回线程的ThreadLocalMap  如上 返回的是当前线程的map
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

	// 创建map也是给线程的ThreadLocalMap进行赋值
	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  1. 当调用get方法时,调用当前线程的ThreadLocalMap来获取当前ThreadLocal的值。
  public T get() {
        Thread t = Thread.currentThread();
        // getMap还是获取当前线程的map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// 从当前map获取值
            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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
    }
  1. Thread中的ThreadLocalMap的hash表为Entry类型的数组,其中Entry类型继承了弱引用,其中的reference为ThreadLocal,又新生命了Object value
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

ThreadLocalMap与HashMap的异同

相同点

  1. 底层都是使用一个数组作为hash表
  2. 都使用hash进行寻址

不同点

  1. 处理hash冲突的方式
    • hashMap采用拉链表法
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 如果当前表为空 初始化表
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
        // 如果计算得到的位置处为空,则给该位置直接放值
            tab[i] = newNode(hash, key, value, null);
        else {
        // 发生了hash冲突
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 如果当前的key等于参数key 则直接复制
                e = p;
            else if (p instanceof TreeNode)
            // 如果是是树节点则插入红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            // 否则遍历链表,将新值放到链表最后
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    
    • ThreadLocalMap采用的是线性探测法
            private void set(ThreadLocal<?> key, Object value) {
    
            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
    		// 获取当前hash表
            Entry[] tab = table;
            int len = tab.length;
            // 计算索引
            int i = key.threadLocalHashCode & (len-1);
    		// 当tab[i]上有值 就一直往下一个找 线性探测
            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();
        }
    
  2. hashMap的键类型是泛型,可以是任意类型,ThreadLocalMap的键类型只能是ThreaLocal
  3. HashMap是线程不安全的,ThreadLocalMap因为在线程中声明所以天生就是线程安全的
  4. 内存管理与泄露风险
    • hashMap都是强引用,当不再引用时回收键值
    • ThreadLocalMap是弱引用,需要在调用结束之后调用remoev()方法,不然会导致无法回收value而发生内存泄露

弱引用

ThreadLocal继承了WeakReference,所以我们也要了解下弱引用。java实现弱引用是通过WeakReference类实现的,当触发gc之后,会直接回收弱引用的对象。如下:

  public static void main(String[] args) {
    WeakReference<Integer> weakReference = new WeakReference<>(300);
    System.out.println(weakReference.get()); // 300
    System.gc();
    System.out.println(weakReference.get()); // null
  }

java缓存

如上这个方法如果使用1而不是300,因为java存在Integer的缓存。

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

如上代码,java的底层会缓存-128到127的值,所以如果比较两个Integer = 100的值会得到true,因为底层其实是一个对象。
同样的还有ByteShortCharacterLongBoolean类。