• ThreadLocal源码分析


    1. 概述

    很多同学对ThreadLocal并不陌生, 但是可能大多数同学可能是知其然不知其所以然, 所以今天就来分析一下ThreadLocal中的奥妙.

    个人知识面不是很广, 很多知识综合不起来, 本文只是针对ThreadLocal的源码进行解析.

    2. 实战

    先来看一个示例吧.

    public class ThreadLocalDemo {
    
        public static void main(String[] args) {
    
            // 定义ThreadLocal
            ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
                @Override
                protected String initialValue() {
                    return "init value.";
                }
            };
    
            // 启动线程A
            new Thread(() -> {
                // set的过程实际上是对当前Thread类实例对象中的属性值threadLocals进行赋值
                // 值为 new ThreadLocalMap(this, firstValue);
                // 其中的this是当前的threadLocal实例对象
                threadLocal.set("我是线程A.");
    
                // get的过程实际上是先获取当前的线程, 然后获取当前线程中的threadLocals属性值(ThreadLocalMap)
                // 然后通过key来获取值, key就是当前的实例对象, 也就是threadLocal实例对象
                System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
    
                // 移除
                threadLocal.remove();
            }, "线程A").start();
    
            // 启动线程B
            new Thread(() -> {
                threadLocal.set("我是线程B.");
                System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
                threadLocal.remove();
            }, "线程B").start();
    
            // 打印主线程中的值
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
            threadLocal.remove();
    
        }
    
    }
    

    看起来很奇怪, 只有一个threadLocal实例对象, 在不同的线程中设置了不同的值, 但是获取的时候却没有混乱. 看起来是不同线程之间的变量各自独立, 它是如何实现的呢? 继续往下看.

    3. Thread与ThreadLocal的关系

    打开Thread的源码发现, Thread类中有一个属性值是threadLocals, 是这样定义的:ThreadLocal.ThreadLocalMap threadLocals = null;, 可以发现实际上引用的是ThreadLocal类中的内部类ThreadLocalMap.

    那么ThreadLocalMap又是什么呢? 通过名字猜测是一个Map, 内部有一个Entry数组存储Entry实例, 而Entry继承了WeakReference(弱引用), 弱引用的特点就是在进行GC的时候, 弱引用会被回收掉. ThreadLocalMap中提供了Map的基本操作, 如set/get/remove.

    这里可以猜测一下ThreadLocalMap中存储的key和value都是什么? 下面会有解答.

    4. ThreadLocal#set

    set无非就是复制操作, 方法名简单, 但是具体实现并不是简单的赋值操作.

    public void set(T value) {
        Thread t = Thread.currentThread(); // 获取当前线程.
        ThreadLocalMap map = getMap(t); // 获取线程实例的threadLocals属性值.
        if (map != null)
            map.set(this, value); // this指的是threadLocal对象. value就是传入的值.
        else
            createMap(t, value); // 创建一个Map, 并设置value值.
    }
    // ----------被调用的方法--------------
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
    

    可以这样理解: Thread类持有ThreadLocalMap类实例, 而ThreadLocalMap类以Map(数组)的形式持有n个Entry(Entry的key为ThreadLocal实例, value为存储数据).

    5. ThreadLocal#get

    同样, get方法也并不是简单的获取.

    public T get() {
        Thread t = Thread.currentThread(); // 获取当前线程
        ThreadLocalMap map = getMap(t); // 获取threadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); // 通过当前的threadLocal实例获取对应的值
            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;
    }
    

    这里比较简单, 就是先获取当前线程, 然后获取线程持有的threadLocalMap对象, 然后通过key(当前的threadLocal)获取对应value值.

    但是这里这个setInitialValue方法是什么呢? 里面调用了initialValue方法, 这里返回了一个null值, 其实我们可以重写这个方法, 如下

    // 定义ThreadLocal
    ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return "init value.";
        }
    };
    

    这样, 方法的返回值就是我们自定义的初始值了.

    6. 关于内存泄漏

    对于Java同学来说, 内存泄漏这个词比较陌生, 但是对于C的同学来说比较熟悉, 就是分配了内存之后没有进行回收, 因为C是手动分配内存手动进行回收, 而Java是自动的. 如果使用不当则会导致内存泄漏.

    关于ThreadLocal的内存泄漏原因, 写文章的时候我还没有理解到位, 大家可以自行百度, 但是这里给出解决方法来避免可能的内存泄漏.

    就是 每次使用完成之后要使用threalocal.remove();从Thread的ThreadLocalMap中移除当前的ThreadLocal.

    7. 总结

    关于ThreadLocal要明白以下几点

    • 了解他的运行原理
    • 避免内存泄漏
  • 相关阅读:
    Team Foundation Sidekicks 2010
    Asp.net页面传值的方式汇总
    轻量级IOC框架Ninject使用
    AutoMapper使用简单总结
    页面请求的方式(Get与Post)
    总结2012 规划2013
    在reset css后两个input之间还是出现默认间隔的问题。
    js学习笔记事件委托
    程序猿工具——svn
    JS 事件添加onclick写法注意。
  • 原文地址:https://www.cnblogs.com/wuqinglong/p/10173579.html
Copyright © 2020-2023  润新知