深入浅出ThreadLocal

2018-05-19  本文已影响59人  马克99号

前言

在多线程并发的情况下,我们经常会设置一个共享变量,使用同步的方式进行访问。避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭。线程封闭的一个典型实现即为ThreadLocal。

什么是ThreadLocal

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one has its own, independently initialized copy of the variable.Instances are typically private static fields in classes that wish to associate state with a thread.

以上是来自的该class的description,可以理解为:线程的本地私有变量,线程与线程之间互不干扰。可以来看一个Demo。

public class Test {
    public static ThreadLocal<Integer> count = new ThreadLocal<>();

    public static final void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            Thread t = new Thread() {
                @Override
                public void run() {
                    count.set(0);
                    for (int i = 1; i < 5; i++) {
                        Integer temp = count.get();
                        count.set(++temp);
                        System.out.println(Thread.currentThread().getId() + ":" + temp+" ");
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            t.start();
        }
    }
}
输出结果为:11:1 12:1 12:2 11:2 12:3 11:3 12:4 11:4 

可以看到代码中的count值在自增的过程中,并没有互相影响。

ThreadLocal实现原理

先来看下ThreadLocal中的get和set方法

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

我们看到无论是get还是set方法,第一步都会找到属于当前线程的threadLocalMap.
当执行set方法时,将值保存在本线程的threadLocalMap中,当执行get方法时,也是取自于该线程的threadLocalMap.
ThreadLocalMap又是什么呢?
字面意思可以看出来是一个类似于Map的数据结构,但事实上ThreadLocalMap类并没有继承Map类。源代码中可以看到(如下),ThreadLocalMap内部是一个Entry数组,而Entry继承于WeakReference,用来保存K:V.

     static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

get方法详细的过程为:
1.先找到该线程的threadLocals对象。
2.找不到threadLocals时去做初始化的工作,否则如3.
3.执行getEntry()方法找到对应的Entry,并返回Entry的value.

    private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

通过以上描述可以看到ThreadLocalMap的key其实是ThreadLocal本身。

hash冲突问题

观察Entry的结构,发现并没有next属性,即不会产生链表,那么如何防止哈希冲突呢?

  private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

每一个ThreadLocal都会有自己的hash值,且该值固定的增量为0x61c88647。
在执行set方法时,会通过hash值定位到Entry数组的i位,此时如果该位置是null,则设置k-v,如果有找到相同的key,则覆盖原有的value,否则继续往下一位寻找。

内存泄漏

由于Entry继承于WeakReference,容易发生内存泄漏,故可以在使用完后执行清理操作,即:

ThreadLocal<String> threadLocal= new ThreadLocal();
try {
    threadLocal.set("I am IronMan");
} finally {
    threadLocal.remove();
}

END

上一篇 下一篇

猜你喜欢

热点阅读