ThreadLocal如何实现多线程间存取独立?

本文详细解释了Java中的ThreadLocal类,它如何在多线程环境中保存线程私有变量,以及如何在请求处理等场景中实现数据共享。ThreadLocal实质上是为每个线程维护一个独立的数据存储区域,确保数据隔离性。
摘要由CSDN通过智能技术生成

ThreadLocal是什么

ThreadLocal 是 java.lang包下的一个类,用于在多线程环境中保存线程私有变量。

会用再说

当一个线程执行过程中需要共享某个值,且这个值属于线程私有,可以使用ThreadLocal。

例如:Web开发中的请求处理过程。每个请求通常由一个线程来处理,而不同请求之间的数据是相互独立的。使用 ThreadLocal 可以很方便地在请求处理过程中共享数据,比如用户信息,看个例子:

public class UserContext {
    // 使用 ThreadLocal 存储用户信息
    private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();

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

    public static User getUser() {
        return userThreadLocal.get();
    }

    public static void clearUser() {
        userThreadLocal.remove();
    }
}

设计一个UserContext 类,用于存储用户信息,内部使用 ThreadLocal 实现。接下来模拟一下请求处理过程。

//模拟请求处理过程
public class WebRequestProcessor {

    //请求处理过程
    public void processRequest(int userId) {
        
        // 从数据库中获取用户信息
        User user = UserRepository.getUserById(userId);

        // 将用户信息保存到UserContext中
        UserContext.setUser(user);

        // 整个请求处理过程中可以随时获取用户信息
        performAuthentication();
        performAuthorization();

        //...

        // 处理完请求后清除用户信息
        UserContext.clearUser();
    }

    private void performAuthentication() {
        // 获取用户身份,并验证...
        User user = UserContext.getUser();
        System.out.println("Authenticating user: " + user.getUsername());
        
    }

    private void performAuthorization() {
        // 获取用户身份,并授权...
        User user = UserContext.getUser();
        System.out.println("Authorizing user: " + user.getUsername());
    }
}

请求处理过程中,从数据库中获取用户信息并设置到UserContext中。整个处理流程中,可以随时通过 UserContext.getUser() 获取当前线程的用户信息。

以上例子没有明确体现出多线程间互不影响,再来看一个简单的例子:

public class ThreadLocalExample {

    //多线程共用的ThreadLocal
    private static ThreadLocal<Integer> threadLocalVariable = new ThreadLocal<>();

    public static void main(String[] args) {
        // 创建并启动三个线程
        Thread thread1 = new Thread(() -> {
            threadLocalVariable.set(1);
            printThreadLocalVariable();
        },"线程1");

        Thread thread2 = new Thread(() -> {
            threadLocalVariable.set(2);
            printThreadLocalVariable();
        },"线程2");

        Thread thread3 = new Thread(() -> {
            threadLocalVariable.set(3);
            printThreadLocalVariable();
        },"线程3");

        thread1.start();
        thread2.start();
        thread3.start();
    }

    private static void printThreadLocalVariable() {
        // 从 ThreadLocal 中获取变量值,并打印
        int value = threadLocalVariable.get();
        System.out.println(Thread.currentThread().getName() + ": ThreadLocal Variable = " + value);
    }
}

执行结果:

线程1: ThreadLocal Variable = 1
线程3: ThreadLocal Variable = 3
线程2: ThreadLocal Variable = 2

使用ThreadLocal作为容器,创建了三个线程,每个线程都通过 threadLocalVariable.set() 方法将自己私有的值存入ThreadLocal,线程执行过程中通过 printThreadLocalVariable() 方法从ThreadLocal 中获取变量值并打印。
从结果可以看出,每个线程取出的值都是自己之前设置过的,属于自己的值,并不会拿错,多线程间独立。

很多人都把物品放到一个公共大容器里,要用的时候也直接从大容器里取,还能保证不拿错???太神奇了!

得去看看原理↓ ↓ ↓

实现原理

上源码,先大致过一下。

public class ThreadLocal<T> {

    //构造方法
    public ThreadLocal() {
    }

    //存入值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, 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();
    }

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

    //获取线程的threadLocals
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

以上是ThreadLocal源码中主要的方法,下面依次看看各个方法的调用过程。

getMap

通过源码发现,每个方法执行前都先获取到_当前线程对象_,把当前线程作为参数传给getMap()方法,而getMap()方法中仅有一行代码:返回当前线程的threadLocals。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

可以看出threadLocals是Thread类的一个成员变量,具体是什么?还得去Thread源码中一探究竟。

class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

threadLocals是一个ThreadLocal.ThreadLocalMap类型的成员变量。通过类型可知,是ThreadLocal类的静态内部类(详细结构见ThreadLocal源码)。
通过ThreadLocal中的ThreadLocalMap结构可以看出,ThreadLocalMap是一个专为_维护线程私有值_而定制的哈希表。
每个线程都有一个ThreadLocalMap,通过ThreadLocal存放的数据都是放到了这里。

原来,存放的数据是在“自己家里”,而非公共容器里。

具体如何存取的??↓ ↓ ↓

set

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);		//获取当前对象内部的map
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

先获取当前线程内部的map。
若非空,将this作为键,参数value作为值,存入map中。因为是通过ThreadLocal对象调用set,所以this就是ThreadLocal对象。
否则,创建一个ThreadLocalMap对象赋值给当前线程的threadLocals变量。

原来如此~

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

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;
}

同样,get时,先获取当前线程内部的map。
非空,则将this作为键,查询出value并返回。
否则!来了一个迷惑行为,调用setInitialValue(),通过方法名感觉是搞一个初始值,进入逛逛,原来是一场null~

remove

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

同样,获取线程内部map,非空,以this为key,移除。

通透了~原来ThreadLocal只是个壳!只是个key,真正的数据,还是存在线程内部(取的时候去自己家里取,怎么可能取错呢),操作数据时,先拿到当前线程的map,以ThreadLocal对象为key,对数据进行存取。

总结

  • ThreadLocal可以当做一个全局大容器使用,多个线程共同往里面存取数据,且保证多线程间存取独立。

  • 实际上,往ThreadLocal中存的数据是放在每个线程内部的map对象中。操作数据前,先获取当前线程的map,以ThreadLocal对象为key进行数据存取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值