ThreadLocal探究

折月煮酒 提交于 2021-01-09 21:47:37

ThreadLocal 是JDK给我们提供的一个工具类,通过该类提供的方法可以操作变量,每个线程往ThreadLocal 中读写数据是线程隔离的,操作的都是线程本地内存里的变量,从而避免了线程安全的问题。

下面这段程序创建了一个ThreadLocal 对象countTL,重写了方法initialValue(当我们调用get方法时,如果还没有通过set方法设置值,它会返回initialValue方法的返回值)。在main方法里启动了两个线程,两个线程在循环里分别调用了increase方法,increase方法首先通过countTL.get()获取变量,然后++,在调用set方法设置新的值。

public class ThreadLocalDemo {

    private static final ThreadLocal<Integer> countTL = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return new Integer(0);
        }

        ;
    };

    public int increase() {
        //如果未执行set方法初始值为initialValue返回的值
        Integer value = countTL.get();
        value++;
        //设置值
        countTL.set(value);
        return value;
    }

    public static void main(String[] args) {
        ThreadLocalDemo d = new ThreadLocalDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                    System.out.println(Thread.currentThread().getName() //
                            + " " + d.increase());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "线程1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                    System.out.println(Thread.currentThread().getName()//
                            + " " + d.increase());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "线程2").start();
    }

}

运行结果:

通过运行结果,可以看出两个线程通过ThreadLocal 操作的变量是两个不同的变量,否则不可能出现递增这种现象。

虽然多个线程调用的是ThreadLocal 相同的方法,但是操作的却是线程自己的本地变量,ThreadLocal是如何做到的呢?

下面我们来看看ThreadLocal的内部结构

每个Thread线程内部都有一个ThreadLocalMap 变量,ThreadLocalMap 是ThreaLocal于内部实现的哈希表存储结构,和HashMap类似,但又不太相同。

ThreadLocal.ThreadLocalMap threadLocals

ThreadLocalMap 里的Entry对象的key就是ThreadLocal对象本身,value就是变量的值。当我们获取变量值得时候,首先可以通过Thread.currentThread()获取到当前线程对象,然后获取线程内部的ThreadLocalMap对象,然后调用ThreadLocalMap 的get方法就可以获取到值。同理设置值也是最后转换为对线程内部的ThreadLocalMap 的操作。

一个ThreadLocal只能存储一个变量,会产生一个Entry对象存储到ThreadLocalMap对象里,如果存储多个变量就需要多个ThreadLocal。

 

get方法:

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的 threadLocals 变量
        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;
            }
        }
        //threadLocal为null则初始化当前线程的 threadLocals
        return setInitialValue();
    }

如果get方法获取map为null 或者 获取元素为变量为null,则会调用setInitialValue方法,如果map为null会创建map,并设置初始值,并返回。

    private T setInitialValue() {
        
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

set方法:set方法也是先取得当前线程里的Map对象,然后操作map里的变量,如果map为null,同样会创建map。

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

remove方法:同样也是取得当前线程里的Map对象,然后调用map的方法操作变量。

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

 

ThreadLocalMap是ThreadLocal里的一个静态内部类,是采用线性探测的方式实现的哈希表,并不是像HashMap那样采用链地址法。ThreadLocal变量如果存在过多的化,极大的增加Hash冲突的可能,影响效率。

    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

值得注意的是这里的Entry继承WeakReference,是一个弱引用。

弱引用:当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。这里你可能会疑问:比如最上面的那个程序,当发生GC的时候,ThreadLocal对象会不会被回收掉,导致get的时候取不到值。实际上不会的,因为ThreadLocal对象还被countTL强引用引用着,只被弱引用关联的对象才会被回收掉。

如果ThreadLocal对象不在被强引用引用着,GC后Entry对象的key就会被回收掉,变为null,但是value是强引用,并不会被回收。所以使用完要调用一下remove方法,remove方法会调用ThreadLocalMap的remove方法,该方法里的expungeStaleEntry(i)会将key为null的值清除掉。

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

 

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!