Java多线程(5)-ThreadLocal

ThreadLocal

ThreadLocal一般用于存储当前线程私有的值,和其他线程不共享的值,并且多个线程根据同一个ThreadLocal对象get获取到的就是独属于当前线程的值,与其他线程具有隔离性,

它的方便之处就是可以不通过方法参数在方法栈从始至终传递一个或多个参数,而是通过一个共享的变量,类似静态类.方法的方式获取值。

自己实现一个ThreadLocal

首先,线程数据隔离,如何隔离?我们可以以线程的id作为唯一主键,对应着该id相应的值,

由此可以选择使用Map数据结构,用作存储,key是线程id,value是线程id对应的值,实现的源代码如下:

public class Main {

    // 用于保存线程的参数对象
    static MyThreadLocal threadLocal = new MyThreadLocal();

    public static void main(String[] args) {
        // main线程设置名字
        threadLocal.set("user2");
        new Thread(() -> {
            // 新线程设置名字
            threadLocal.set("user1");
            // 输出当前新线程对应的名字
            doSomething();
        }).start();
        // 输出main方法对应的名字
        doSomething();
    }

    // 输出当前线程的名字
    private static void doSomething() {
        System.out.println("thread: " + Thread.currentThread().getName()+ ": " + threadLocal.get());
    }

    private static class MyThreadLocal {
        // 使用map存储线程的名字
        Map<Long, String > data = new ConcurrentHashMap<>();

        // get当前线程id对应的名字
        public String get() {
            return data.get(Thread.currentThread().getId());
        }

        // set 当前id对应的名字
        public void set(String userName) {
            data.put(Thread.currentThread().getId(), userName);
        }
    }

}

运行结果:

可以在看main方法中的代码,调用了两次 doSomething 方法,但是得到的结果是不同的,这再次说明了多线程编程的反直觉性,

同样我们可以通过ThreadLocal在同一个方法中,获取到当前调用方的线程信息,做出不同的处理结果,给予当前线程,

例如有两个线程,一个是管理员,一个是普通工作人员,他们都来调用doSomething方法,方法中的事情是一样的,但是说出来的名字却不相同,一个是我是管理员,一个是我是普通工作人员,

因为两个线程身份不同,所以输出来的结果自然不同。

ThreadLocal真正的实现

我们将上放的MyThreadLocal类替换为下方的类即可,下方的类没有使用Map,而是jdk的ThreadLocal类,

static class UserContext {

        static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

        public void set(Integer userId) {
            threadLocal.set(userId);
        }

        public Integer get() {
            return threadLocal.get();
        }

 }

根据我们自己的设想,在ThreadLocal类中,应该也有一个Map,用于保存每个线程的信息,

但其实在该类中,我们并没有找到相关的Map,所以我们查看该类的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();
    }

看可以看到其中 ThreadLocalMap map = getMap(t); , 这里有一个获取map的地方,其源代码是:

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

可以看到,这里的ThreadLocalMap是从当前线程中的变量中获取的, 为什么是存在线程中,而不是存储在ThreadLocal类当中呢?

ThreadLocal的类头注释当中有说,每个线程当中都有一份隐式的拷贝,这是为了让线程相应的数据随着线程销毁后也跟着被垃圾回收掉,

避免ThreadLocal类引用了一些已经销毁的线程的数据,导致该垃圾数据无法回收。


 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).

那么这个map是什么时候被创建并赋值的呢?可以看下set方法:

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

set方法中,先获取map,如果获取不到则进行创建, 并对线程的属性进行赋值:

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

可以看到,JDK源码的考虑非常之详细,一般正常想到的通过一个大的map去保存,但是jdk同时还考虑到数据的垃圾回收,值得学习该思想~ 多阅读java源代码会有很多意向不到的收货~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值