关于ThreadLocal的说明

2019-07-30  本文已影响0人  华木公子

一 ThreadLocal的使用场景

当多个线程需要使用同一共享变量,但是每个线程又希望有自己独立的值,互不干扰时。

二 ThreadLocal的原理

  1. 在Thread类中,有一个特殊的局部变量ThreadMap,这个Map不是通用Map,是单独构造的一个类似Map类的类。
public
class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

当Thread实例化时,一个thread就有一个对应的Map(私有)

  1. 当在thread线程中对threadlocal共享变量进行存值操作时,当前线程的私有Map会把threadlocal变量this作为key存储,值为对应需保存的value。
    当在thread线程中对threadlocal共享变量进行取值操作时,当前线程的私有Map会把threadlocal变量this作为key从map中取值,所获得的值为对应之前保留的value。
    由于map是私有的,所以其他线程是不可能操作这个线程的map,从而也就达到了互不影响的效果。

【测试代码如下】:

public class ThreadLocalDemo {
    private static ThreadLocal<String> myLocal = new ThreadLocal<String>();

    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                myLocal.set("A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(myLocal.get());
            }
        });
        myLocal.set("main");
        threadA.start();
        try {
            threadA.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(myLocal.get());
    }
}

当对myLocal.set("A");执行时,ThreadLocal代码如下:

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

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();
    }
}
  1. 一个threadlocal变量对应一个值,一个线程中可以使用多个threadlocal变量来存取多个值。但是一个线程只有一个map,所以可能会存在key冲突的问题,那么当key的hashcode冲突时,就会继续往下一个数组空间存放,直到不冲突。这就存在效率问题,取值时,就需要根据hashcode比较,取出threadlocal对象后还要比较(==)是否是同一个对象,如果是同一个对象,才会去取它对应的值。
    ThreadLocal.ThreadLoclMap 的代码如下:
 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使用的注意事项

  1. 内存泄漏问题
    threadmap的entry本身key是弱引用可以被回收,但是value是强引用,不会被GC回收。如果使用了线程池,那么value就会一直存在,导致内存泄漏。
上一篇下一篇

猜你喜欢

热点阅读