ThreadLocal

Posted 叶沐雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal相关的知识,希望对你有一定的参考价值。

一句话描述

threadLocal,是线程本地变量,每个thread实例持有一个ThreadLocalMap类型的threadLocals属性,map的key为ThreadLocal变量,value为调用threadLocal.set()的值,也就是真正有用的数据。

基础

简单例子

package com.xy.thread.threadlocal.ch01;

/**
 * <p>
 *  常用写法:
 *  1.如果需要设置初始值,可以使用匿名类,或者定义子类继承Threadlocal
 *  2.不需要初始值可以直接new ThreadLocal()创建对象
 * </p>
 *
 * @author liufuquan
 * @since 2022-02-15
 */
public class NumService implements Runnable

    //匿名类语法,继承类或者实现接口
    // initialValue方法为protected方法,继承ThreadLocal类重写initialValue方法设置初始值
    private ThreadLocal<Integer> numThreadLocal = new ThreadLocal<Integer>() 
        @Override
        protected Integer initialValue() 
            return 0;
        
    ;


    @Override
    public void run() 
        for (int i = 0; i < 5; i++) 
            Integer integer = numThreadLocal.get();
            System.out.println(Thread.currentThread().getName() + ":" + integer);
            numThreadLocal.set(++integer);
        
        //使用完之后,要清除
        numThreadLocal.remove();
    

package com.xy.thread.threadlocal.ch01;

import java.util.Scanner;

/**
 * <p>
 *
 * </p>
 *
 * @author liufuquan
 * @since 2022-02-15
 */
public class NumServiceTest 
    public static void main(String[] args) 
        for (int i = 0; i < 2; i++) 
            new Thread(new NumService()).start();
        
        Scanner input = new Scanner(System.in);
        input.nextLine();
    

线程关联的原理

简介

/**
* This class provides thread-local variables. 
**/
public class ThreadLocal<T> 

thread-local variables,官方的注释,说明它的含义,线程本地变量。
ThreadLocal 并不是一个独立的存在, 它与 Thread 类是存在耦合的, java.lang.Thread 类针对 ThreadLocal 提供了如下支持:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class.
     与此线程有关的 ThreadLocal 值。此映射由 ThreadLocal 类维护*/
ThreadLocal.ThreadLocalMap threadLocals = null;

每个线程thread对象中有一个Thread.ThreadLocalMap字段,使用时,调用ThreadLocal对象的set时,放到threadLocalMap中,key为ThreadLocal,value为设置的值。(每个线程都将自己维护一个 ThreadLocal.ThreadLocalMap 类在上下文中; map的key是ThreadLocal对象,value是与线程绑定的,真正用的用到的值。 )

uml关系

代码解析

ThreadLocal.set()方法,set 方法其实是将 target value 放到当前线程的 ThreadLocalMap 中, 而 ThreadLocal 类自己仅仅作为该 target value 所对应的 key

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

get 方法也是类似的道理, 从线程的 ThreadLocalMap 中获取以当前 ThreadLocal 为 key 对应的 value

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

如果没有 set 过 value, 此处 get() 将返回 null或者initialValue()设置的默认值。不过 initialValue() 方法是一个 protected 方法, 需要重写逻辑实现自定义的初始默认值。

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;


protected T initialValue() 
    return null;

使用场景

Spring中@Transactional注解,使用ThreadLocal存储数据库连接connection,保证事务方法每次拿到的都是同一个connection
Spring中Bean在singleton作用域时,使用ThreadLocal解决共享变量的线程安全问题

进阶

ThreadLocalMap数据结构

ThreadLocalMap是哈希表,但是与HashMap不同的是,出现hash冲突后的解决办法:hashmap使用拉链法,ThreadLocalMap使用线性探索法,发生冲突就在数组中向后寻找空的位置插入。

InheritableThreadLocal

一句话描述

InheritableThreadLocal,可继承的线程本地变量,每个线程实例持有ThreadLocalMap类型的inheritableThreadLocals,父线程创建子线程时会将父线程的inheritableThreadLocals复制到子线程的inheritableThreadLocals,从而实现线程本地变量可以传递到子线程。
InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量「复制一份到子线程的inheritableThreadLocals」变量中。

class Thread implements Runnable
     /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

线程的init方法

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) 
    
    //(1)获取当前线程(父线程)
    Thread parent = currentThread();
    //(2)父线程的inheritableThreadLocal和inheritThreadLocals=true
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    //(3)父线程的inheritableThreadLocals赋值给子线程的inheritableThreadLocals
    this.inheritableThreadLocals = 
         ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

参考资料

https://mp.weixin.qq.com/s/x_dpL66o5S7x_PjncWLvUw
https://mp.weixin.qq.com/s/KbB_3JdR56Mw8O2gJEP2OA

以上是关于ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal(一)设计ThreadLocal的目的

ThreadLocal 的原理讲述 + 基于ThreadLocal实现MVC中的M层的事务控制

对ThreadLocal的一些理解

ThreadLocal源码解析-Java8

ThreadLocal

手撕面试题ThreadLocal!!!