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

posted @ 2017-09-28 08:41 jiajun_geek 阅读(...) 评论(...) 编辑 收藏