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/
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。若是以为还有帮助的话,能够点一下右下角的【推荐】,但愿可以持续的为你们带来好的技术文章!想跟我一块儿进步么?那就【关注】我吧。html

相关文章
相关标签/搜索