【Java】ThreadLocal原理与使用场景

2023-12-31 20:30:59

ThreadLocal原理:

字段:

//ThreadLocal对象的哈希码
private final int threadLocalHashCode = nextHashCode();


//生成ThreadLocal对象的哈希码时,需要用到该对象,从0开始
private static AtomicInteger nextHashCode =
        new AtomicInteger();


//哈希码的增长值
private static final int HASH_INCREMENT = 0x61c88647;


? ? ? ? nextHashCode()方法:


private static int nextHashCode() {
        //通过AtomicInteger对象生成ThreadLocal对象的哈希码并返回
        //步长为:HASH_INCREMENT
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

方法实现:

? ? ? ? (1)get()方法:

public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //判断ThreadLocalMap对象是否为空
        //map不为null说明有键值对
        if (map != null) {
            //在map中,ThreadLocal作为key
            ThreadLocalMap.Entry e = map.getEntry(this);
            //如果key不为空
            if (e != null) {
                @SuppressWarnings("unchecked")
                //通过key获取值并返回
                T result = (T)e.value;
                return result;
            }
        }
        //如果map为null,则创建map
        //当前ThreadLocal对象作为key,null作为value存入map中
        //返回null
        return setInitialValue();
    }

? ? ? ? (2)set()方法:

public void set(T value) {
        //获取当前线程的ThreadLocalMap对象
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        
        //如果map不为null
        if (map != null)
            //将当前ThreadLocal作为key
            //参数value作为值,存入ThreadLocalMap中
            map.set(this, value);
        else
            //map为null,则创建map
            //将当前ThreadLocal对象作为firstKey,参数value作为firstValue
            createMap(t, value);
    }

通过set方法,我们可以知道:

? ? ? ? 1、线程调用ThreadLocal对象的set方法时,会获取当前线程的ThreadLocalMap对象。

? ? ? ? 2、随后会将当前ThreadLocal对象作为key,参数value作为值存储到线程内部的ThreadLocalMap对象中。

? ? ? ? 3、这意味着即使ThreadLocal是多个线程共享的变量也不会存在线程安全问题,因为每个线程都只在操作自己的ThreadLocalMap,ThreadLocal只是作为key保存在map中。

? ? ? ? (3)remove()方法:

public void remove() {
         //获取当前线程的ThreadLocalMap对象
         ThreadLocalMap m = getMap(Thread.currentThread());
         //如果map不为null
         if (m != null)
             //将当前ThreadLocal对象从ThreadLocalMap中移除
             m.remove(this);
     }

ThreadLocalMap、ThreadLocal、Thread三者之间的联系:

测试:

? ? ? ? (1)User类:

package test;

public class User {

    private Integer id;

    private String name;


    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }


    public User(){

    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

? ? ? ? (2)UserHolder类:

package test;

public class UserHolder {


    private static final ThreadLocal<User> threadLocal = new ThreadLocal<>();

    public static void set(User user){
        threadLocal.set(user);
    }


    public static User get(){
        User user = threadLocal.get();
        return user;
    }
}

? ? ? ?ThreadLocal作为静态字段存在于UserHolder类中,UserHolder的set方法、get方法实际上都是由ThreadLocal实现,这里使用到了组合的思想。

? ? ? ? (3)ThreadLocalTest类:

package test;

public class ThreadLocalTest {


    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            System.out.println("Thread-1线程运行,存储User对象到TheadLocal中...");
            createAndHold(1,"张三");
            User user = UserHolder.get();
            System.out.println(user);
        }, "Thread-1").start();
        
        System.out.println("main线程阻塞...");
        Thread.sleep(3000);
        System.out.println("main线程运行,获取UserHolder存储的User对象...");
        User user = UserHolder.get();
        System.out.println("user对象:" + user);
    }

    private static void createAndHold(Integer id,String name){
        User user = new User(id, name);
        UserHolder.set(user);
    }
}

代码解读:

? ? ? ? 1、main线程一开始会阻塞3s,Thread-1线程运行,将User对象存入ThreadLocal并打印。

? ? ? ? 2、main线程解除阻塞后,会去获取ThreadLocal中存储的User对象;照理来说,ThreadLocal对象是静态成员,对于两个线程来说是共享变量,此时main线程应该会获取到Thread-1存入的User对象。

? ? ? ? 3、但我们知道,ThreadLocal真正操作的是每个线程内部的ThreadLocalMap,ThreadLocal对象只作为key存储到每个线程自己的ThreadLocalMap中,我们可以通过key找到value,实现线程间的隔离。

测试结果:

ThreadLocal使用场景:

? ? ? ? (1)多线程隔离 多个线程使用ThreadLocal访问共享变量,每个线程都会得到共享变量的一个副本,后续多个线程自己所属副本的所有操作不会冲突,没有线程安全问题。

????????(2)线程内共享ThreadLocal 可以用于在整个线程生命周期内共享这些上下文信息,而不需要显式地传递参数。

????????(3)存储用户信息在Web应用中,可能会在用户登录后将用户身份信息存储在 ThreadLocal 中,以便在整个请求处理过程中方便地访问用户信息,而不必在每个方法中都传递用户信息参数。

? ? ? ?

文章来源:https://blog.csdn.net/Annancqxxx/article/details/135317885
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。