• java基础解析系列(七)---ThreadLocal原理分析


    java基础解析系列(七)---ThreadLocal原理分析

    目录

    作用

    • 与同步机制区分开来,同步机制是为了解决在共享情况下并发导致的问题。而ThreadLocal是避免了共享
    • 在多线程情况下,为了避免共享,我们可以采用多线程多实例的方式,也可以使用ThreadLocal来避免共享冲突

    什么是ThreadLocal

    • ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定

    实验

    public class T {
        ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
        public void set() {
            longLocal.set(Thread.currentThread().getId());
        }
        public long getLong() {
            return longLocal.get();
        }
        public static void main(String[] args) throws InterruptedException {
            final T test = new T();
            test.set();
            Thread thread1 = new Thread(){
                public void run() {
                    test.set();
                    System.out.println("线程一:"+test.getLong());
                };
            };
            thread1.start();
            thread1.join();
            System.out.println("main线程:"+test.getLong());
            System.out.println("没有发生值的覆盖,两个线程保存的值是不同的");
        }
    }
    
    • 输出: 线程一:11 main线程:1 没有发生值的覆盖,两个线程保存的值是不同的
    • 证明ThreadLocal确实为变量在每个线程中都创建了一个副本

    Thread的成员

    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    • Thread有一个ThreadLocalMap成员

    ThreadLocal的set方法

    179    public void set(T value) {
    180        Thread t = Thread.currentThread();
    181        ThreadLocalMap map = getMap(t);
    182        if (map != null)
    183            map.set(this, value);
    184        else
    185            createMap(t, value);
    186    }
    
    • 181行通过当前线程获取ThreadLocalMap
    212    ThreadLocalMap getMap(Thread t) {
    213        return t.threadLocals;
    214    }
    
    • 185行如果当前线程的成员threadLocals还是空的,创建一个map
    224    void createMap(Thread t, T firstValue) {
    225        t.threadLocals = new ThreadLocalMap(this, firstValue);
    226    }
    
    • 将当前的ThreadLocal对象和value作为参数
    328        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    329            table = new Entry[INITIAL_CAPACITY];
    330            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    331            table[i] = new Entry(firstKey, firstValue);
    332            size = 1;
    333            setThreshold(INITIAL_CAPACITY);
    334        }
    

    • 通过ThreadLocalMap的构造方法可以看到,该方法创建一个Entry数组,然后通过传入的key(当前ThreadLocal对象)计算在数组中的下标,然后将Entry放入数组。
    • Thread的ThreadLocalMap存放的Entry,键是不同的ThreadLoacal对象,也就是说一个线程绑定多个ThreadLocal对象
    • 那么也就是说,ThreadLocal设置值的时候,这个值是存放在当前线程的一个map(这个map存放了多个ThreadLocal对象)里面,因此,不同线程之间即避免了共享

    ThreadLocalMap的set方法

    416        private void set(ThreadLocal key, Object value) {
    417
    418            // We don't use a fast path as with get() because it is at
    419            // least as common to use set() to create new entries as
    420            // it is to replace existing ones, in which case, a fast
    421            // path would fail more often than not.
    422
    423            Entry[] tab = table;
    424            int len = tab.length;
    425            int i = key.threadLocalHashCode & (len-1);
    426
    427            for (Entry e = tab[i];
    428                 e != null;
    429                 e = tab[i = nextIndex(i, len)]) {
    430                ThreadLocal k = e.get();
    431
    432                if (k == key) {
    433                    e.value = value;
    434                    return;
    435                }
    436
    437                if (k == null) {
    438                    replaceStaleEntry(key, value, i);
    439                    return;
    440                }
    441            }
    442
    443            tab[i] = new Entry(key, value);
    444            int sz = ++size;
    445            if (!cleanSomeSlots(i, sz) && sz >= threshold)
    446                rehash();
    447        }
    
    • 分析这段代码,他解决hash冲突的办法不同与hashmap使用链表来解决冲突问题。通过计算key的hashcode获取数组中的下标后,然后进入427行,432判断要放入的键是否和该下标中原来的键相同,是的话进行值的覆盖。如果为空的,放入该Entry。如果不同的话且不为空,看当前下标+1的位置,同样进入循环。依次执行下去。

    ThreadLocal的get方法

    142    public T get() {
    143        Thread t = Thread.currentThread();
    144        ThreadLocalMap map = getMap(t);
    145        if (map != null) {
    146            ThreadLocalMap.Entry e = map.getEntry(this);
    147            if (e != null)
    148                return (T)e.value;
    149        }
    150        return setInitialValue();
    151    }
    
    • 可以发现,执行get方法的时候,也是先获取当前线程,然后获得该线程的一个ThreadLocalMap成员,通过这个map和和当前的ThreadLocal对象作为键,来获取value

    内存泄露

    271        static class Entry extends WeakReference<ThreadLocal> {
    
    • 如果ThreadLocal不使用弱引用(这篇文章有介绍),那么当ThradLocal th=null时候,因为ThreadLocalMap仍然有th的强引用,所以并不能回收。而如果key使用弱引用的时候,th为null的时候,下次回收的时候就会将这个key回收
    • 但是有一个问题,虽然这个key可以被回收,但是这个value仍然有强引用,并不能回收。如果当前线程不结束,并且不调用set/get/remove方法(这些方法会对key为null的entry进行释放),这片内存会被一直占用。这就是内存泄露的原因
    • 因此在用完ThreadLocal的时候,记得执行remove方法,避免内存泄露

    简单总结

    • 同一个ThreadLocal对象,不同的线程,不同的ThreadLocal对象的值

    我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

    作者:jiajun 出处: http://www.cnblogs.com/-new/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

  • 相关阅读:
    冒泡排序python实现
    mysql主从配置
    函数当作参数传递
    使用xml.dom.minidom创建xml
    php得到所有的汉字
    Cannot load D:/phpenv/php/php548/php5apache2_2.dll错误解决
    php新特性 traits 简单方法复用
    【转】在 Windows 下为 PHP 5.4 安装 PEAR、PHPUnit 及 phpDoc2
    wxpython基础框架
    MFC中文档视图框架和文档模板之间的关系
  • 原文地址:https://www.cnblogs.com/-new/p/7604420.html
Copyright © 2020-2023  润新知