ThreadLocal用法及原理

与Synchonized的对照: ThreadLocal和Synchonized都用于解决多线程并发訪问。但是ThreadLocal与synchronized有本质的差异。synchronized是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程訪问。而ThreadLocal为每一个线程都提供了变量的副本,使得每一个线程在某一时间訪问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通讯时能够得到数据共享。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。数据库


 

用法数组

把要线程隔离的数据放进ThreadLocaltomcat

1static ThreadLocal<T> threadLocal = new ThreadLocal<T>() {
2    protected T initialValue() {
3        这里通常new一个对象返回
4    }
5}

 

线程获取相关数据的时候只要服务器

1threadLocal.get();

 

想修改、赋值只要多线程

1threadLocal.set(val)

 


 

使用场景架构

如上面说到的,ThreadLocal是用于线程间的数据隔离,ThreadLocal为每一个线程都提供了变量的副本。并发

  • 举例1:联想一下服务器(例如tomcat)处理请求的时候,会从线程池中取一条出来进行处理请求,若是想把每一个请求的用户信息保存到一个静态变量里以便在处理请求过程当中随时获取到用户信息。这时候能够建一个拦截器,请求到来时,把用户信息存到一个静态ThreadLocal变量中,那么在请求处理过程当中能够随时从静态ThreadLocal变量获取用户信息。
  • 举例2:Spring的事务实现也借助了ThreadLocal类。Spring会从数据库链接池中得到一个connection,然会把connection放进ThreadLocal中,也就和线程绑定了,事务须要提交或者回滚,只要从ThreadLocal中拿到connection进行操做。

 


 

原理分析this

一、get()方法线程

1public T get() {
 2    Thread t = Thread.currentThread();
 3    ThreadLocalMap map = getMap(t);
 4    if (map != null) {//当map已存在
 5        ThreadLocalMap.Entry e = map.getEntry(this);
 6        if (e != null) {
 7            @SuppressWarnings("unchecked")
 8            T result = (T)e.value;
 9            return result;
10        }
11    }
12    return setInitialValue();//初始化值
13}
14
15ThreadLocalMap getMap(Thread t) {
16    return t.threadLocals;
17}

 

上面先取到当前线程,而后调用getMap方法获取对应的ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,而后Thread类中有一个这样类型成员,因此getMap是直接返回Thread的成员

1ThreadLocal.ThreadLocalMap threadLocals = null; 来看下ThreadLocal的内部类ThreadLocalMap源码,留个大体印象对象

1static class ThreadLocalMap {
 2    private static final int INITIAL_CAPACITY = 16;//初始数组大小
 3    private Entry[] table;//每一个能够拥有多个ThreadLocal
 4    private int size = 0;
 5    private int threshold;//扩容阀值
 6    static class Entry extends WeakReference<ThreadLocal<?>> {
 7        Object value;
 8
 9        Entry(ThreadLocal<?> k, Object v) {
10            super(k);
11            value = v;
12        }
13    }
14
15    private Entry getEntry(ThreadLocal<?> key) {
16        int i = key.threadLocalHashCode & (table.length - 1);
17        Entry e = table[i];
18        if (e != null && e.get() == key)
19            return e;
20        else
21            return getEntryAfterMiss(key, i, e);
22    }
23
24    private void set(ThreadLocal<?> key, Object value) {
25        Entry[] tab = table;
26        int len = tab.length;
27        int i = key.threadLocalHashCode & (len-1);
28        for (Entry e = tab[i];
29             e != null;
30             e = tab[i = nextIndex(i, len)]) {
31            ThreadLocal<?> k = e.get();
32            if (k == key) {
33                e.value = value;
34                return;
35            }
36            if (k == null) {
37                    //循环利用key过时的Entry
38                replaceStaleEntry(key, value, i);
39                return;
40            }
41        }
42        tab[i] = new Entry(key, value);
43        int sz = ++size;
44        if (!cleanSomeSlots(i, sz) && sz >= threshold)
45            rehash();
46    }
47    ...
48}

 

能够看到有个Entry内部静态类,它继承了WeakReference,总之它记录了两个信息,一个是ThreadLocal<?>类型,一个是Object类型的值。getEntry方法则是获取某个ThreadLocal对应的值,set方法就是更新或赋值相应的ThreadLocal对应的值。里面涉及到扩容策略、Entry哈希冲突、循环利用等等再也不深刻,留个大体印象就好。

回顾下get()方法中的代码

1if (map != null) {
2    ThreadLocalMap.Entry e = map.getEntry(this);
3    if (e != null) {
4        @SuppressWarnings("unchecked")
5        T result = (T)e.value;
6        return result;
7    }
8}
9return setInitialValue();

 

map为null或e为null就会走到setInitialValue,若是咱们是第一次get()方法,那map会是空的,因此接下来先看setInitialValue()方法

1private T setInitialValue() {
 2        //调用咱们实现的方法获得须要线程隔离的值
 3    T value = initialValue();
 4    Thread t = Thread.currentThread();
 5    //拿到相应线程的ThreadLocalMap成员变量
 6    ThreadLocalMap map = getMap(t);
 7    if (map != null)
 8        map.set(this, value);
 9    else
10        createMap(t, value);
11    return value;
12}

 

上面initialValue就是实例化ThreadLocal要实现的方法,这里又取了线程的ThreadLocalMap,不为空就把值set进去(键为TreadLocal自己,值就是initialValue返回的值);为空就建立一个map同时添加一个值进去,最后返回value。

map.set(this, value)这句代码在上面的ThreadLocalMap源码中能够看到大体流程,下面看看createMap()作了什么事

1void createMap(Thread t, T firstValue) {
 2    t.threadLocals = new ThreadLocalMap(this, firstValue);
 3}
 4
 5
 6ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
 7    table = new Entry[INITIAL_CAPACITY];
 8    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
 9        //建立一个Entry,加入数组
10    table[i] = new Entry(firstKey, firstValue);
11    size = 1;
12    setThreshold(INITIAL_CAPACITY);
13}

 

能够看到在new ThreadLocalMap以后,就会建立一个Entry加入到数组中,最后把ThreadLocalMap的引用赋值给Thread的threadLocals成员变量

在回顾下get()方法中的代码

1if (map != null) {
2    ThreadLocalMap.Entry e = map.getEntry(this);
3    if (e != null) {
4        @SuppressWarnings("unchecked")
5        T result = (T)e.value;
6        return result;
7    }
8}
9return setInitialValue();

 

如今map不会为空了,再次调用get方法就会调用map的getEntry方法(上面的ThreadLocalMap源码中能够看到大体流程),拿到相应的Entry,而后就能够拿到相应的值返回出去


 

二、set方法

分析完get()方法,那么set()方法就天然而然的明白了,就再也不赘述

1public void set(T value) {
2    Thread t = Thread.currentThread();
3    ThreadLocalMap map = getMap(t);
4    if (map != null)
5        map.set(this, value);
6    else
7        createMap(t, value);
8}

 


 

总结

  • 原理

ThreadLocal的实现原理是,在每一个线程中维护一个Map,键是ThreadLocal类型,值是Object类型。当想获取ThreadLocal的值时,就从当前线程中拿出Map,而后在把ThreadLocal自己做为键从Map中拿出值返回。

  • 优缺点

 

  • 优势:

提供线程内的局部变量。每一个线程都本身管理本身的局部变量,互不影响

  • 缺点:

内存泄漏问题。能够看到ThreadLocalMap中的Entry是继承WeakReference的,其中ThreadLocal是以弱引用形式存在Entry中,若是ThreadLocal在外部没有被强引用,那么垃圾回收的时候就会被回收掉,又由于Entry中的value是强引用,就会出现内存泄漏。虽然ThreadLocal源码中的会对这种状况进行了处理,但仍是建议不须要用TreadLocal的时候,手动调remove方法。

 

架构技术进阶资料

 

+q q-q u n:948 368 769(免费获取如下资料)

+q q-q u n:948 368 769(免费获取如下资料)

相关文章
相关标签/搜索