ThreadLocal

前言

ThreadLocal<T>多用在多线程数据隔离(只能隔离一个T类型的值),如果需要多个,则需要创建多个ThreadLocal。

java 复制代码
private static ThreadLocal<String> sThreadLocal = new ThreadLocal<>();
public static void testMain(){
    for (int i = 0; i < 3; i++) {
        String threadName = "thread"+i;
        new Thread(() -> {
            sThreadLocal.set(threadName);
            System.out.println("threadName:"+Thread.currentThread().getName()+" name:"+sThreadLocal.get());
        },threadName).start();
    }
}
输出:
threadName:thread0 name:thread0
threadName:thread1 name:thread1
threadName:thread2 name:thread2

每个线程通过ThreadLocal对自己存储的数据没有出现脏读的情况(拿到的值是自己存的)。

ThreadLocal怎么做到在多线程间数据隔离的

ThreadLocal是怎么做到多线程数据隔离的,看下ThreadLocal的set方法:

java 复制代码
public void set(T value) {
    // 拿到当前线程
    Thread t = Thread.currentThread();
    // 拿到当前线程的ThreadLocalMap,把value存储进去
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // this 是ThreadLocal(自己创建的sThreadLocal用来存储多线程数据结构),用来维持多个副本ThreadLocal设计。
        map.set(this, value);
    else
        createMap(t, value);
}

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // this 是ThreadLocal(自己创建的sThreadLocal用来存储多线程数据结构),和上面set中的this一致。
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

// ThreadLocalMap 以Thread作为key,以ThreadLocalMap作为value
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
// 内部类
static class ThreadLocalMap {
    // 内部类 弱引用:处理非常大和生命周期非常长的线程,哈希表使用弱引用作为key
    static class Entry extends WeakReference<ThreadLocal<?>> {
        // key:ThreadLocal value:我们数据隔离的位置
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

可以看到每个线程都有自己的数据区threadLocals(ThreadLocal.ThreadLocalMap),当往我们自定义的sThreadLocal调用set(value)存储的时候,会拿当前线程作为key进行存储。当我们调用get()获取的时候根据当前线程(作为key)进行获取。实现多线程通过ThreadLocal<T>实现数据隔离。

  • 每个线程都有自己的数据区(ThreadLocal.ThreadLocalMap)存储属于自己线程的数据
  • ThreadLocal<T>的set(存储)和get(获取)根据当前线程来处理

引用传递

java 复制代码
static NumIndex numIndex = new NumIndex();
private static ThreadLocal<NumIndex> sThreadLocal = new ThreadLocal<NumIndex>() {
    @Nullable
    @Override
    protected NumIndex initialValue() {
        // 这里注意不要这样写。会导致多线程安全问题,因为numIndex不具备多线程安全。这里是每个线程的初始化值,不能把其他线程初始化的值写道这里。
        return numIndex;
    }
};

static class NumIndex {
    int num = 0;

    public void increment() {
       num++;
    }
}

public static void testMain() {
    for (int i = 0; i < 3; i++) {
        String threadName = "thread" + i;
        new Thread(() -> {
            NumIndex index = sThreadLocal.get();
            index.increment();
            sThreadLocal.set(index);
            System.out.println("threadName:" + Thread.currentThread().getName() + " name:" + sThreadLocal.get().num);
        }, threadName).start();
    }
}
输出:
threadName:thread0 name:1
threadName:thread2 name:3
threadName:thread1 name:2

明显出现了多线程并发导致的数据不安全。 在使用ThreadLocal重写initialValue(一般不要重写,否则需要考虑并发问题)要注意。注意值传递和引用传递的区别。

vbnet 复制代码
private static ThreadLocal<Integer> sThreadLocal = new ThreadLocal<Integer>() {
    @Nullable
    @Override
    protected Integer initialValue() {
        return 0;
    }
};

public static void testMain() {
    for (int i = 0; i < 3; i++) {
        String threadName = "thread" + i;
        new Thread(() -> {
            Integer index = sThreadLocal.get();
            sThreadLocal.set(index+5);
            System.out.println("threadName:" + Thread.currentThread().getName() + " name:" + sThreadLocal.get());
        }, threadName).start();
    }
}
输出:
threadName:thread0 name:5
threadName:thread2 name:5
threadName:thread1 name:5

上面是值传递,因为是数据隔离的不会叠加。initialValue只要重写后返回的值(可变的)不是多多线程共享的变量则不会出现线程安全问题。

总结

  • 每个ThreadLocal只能保存一个变量副本,如果想保存多个,则需要创建多个ThreadLocal(从set和get中的this可以体现出来)
  • ThreadLocal内部的ThreadLocalMap的内部类Entry是弱引用
  • 每次用完ThreadLocal,都调用它的remove方法,清理数据
相关推荐
TheNextByte129 分钟前
Android USB文件传输无法使用?5种解决方法
android
quanyechacsdn2 小时前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
程序员陆业聪2 小时前
聊聊2026年Android开发会是什么样
android
编程大师哥3 小时前
Android分层
android
极客小云4 小时前
【深入理解 Android 中的 build.gradle 文件】
android·安卓·安全架构·安全性测试
Juskey iii4 小时前
Android Studio Electric Eel | 2022.1.1 Patch 2 版本下载
android·ide·android studio
Android技术之家4 小时前
2025年度Android行业总结:AI驱动生态重构,跨端融合开启新篇
android·人工智能·重构
洞见前行5 小时前
Android第二代加固技术原理详解(附源码)
android
风清云淡_A5 小时前
【JetCompose】入门教程实战基础案例01之显隐动画
android
2501_916007475 小时前
iPhone APP 性能测试怎么做,除了Instruments还有什么工具?
android·ios·小程序·https·uni-app·iphone·webview