学习笔记:ThreadLocal使用及注意事项

2020-07-01  本文已影响0人  大力papa

本文仅供学习交流使用,侵权必删。
不作商业用途,转载请注明出处。

自JDK1.2版本起,java提供了java.lang.ThreadLocal。ThreadLocal为使用该变量的线程都提供了相互独立的副本,实现线程间的数据隔离。

ThreadLocal的使用场景和源码分析

 public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> tl = new ThreadLocal();
        ThreadLocal<String> tl2 = new ThreadLocal();
        new Thread(() -> {
            tl.set("t1_threadlocal");
            tl2.set("t1_threadlocal_2");
            System.out.println("t1: " + tl.get());
            System.out.println("t1: " + tl2.get());
            System.out.println("t1: " + tl.get());
            tl.remove();
        }, "t1").start();

        new Thread(() -> {
            tl.set("t2_threadlocal");
            System.out.println("t2: " + tl.get());
            System.out.println("t2: " + tl2.get());
        }, "t2").start();
    }

initialValue()

initialValue()为ThreadLocal要存储的数据类型指定一个初始化值,不重写该方法默认是返回null

 public static void main(String[] args) {

        ThreadLocal<String> tl = new ThreadLocal<String>() {
            @Override
            protected String initialValue() {
                return "hello";
            }
        };

        new Thread(()->{
            System.out.println("tl::get : "+tl.get());
        }).start();
        //返回我们定义的初始化值hello
    }

set(T value)

set方法把当前ThreadLocal和我们需要设置的值绑定设置到当前线程的ThreadLocalMap中

ThreadLocal::set
 public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//从当前线程中获取ThreadLocalMap
        if (map != null)
            map.set(this, value);//调用ThreadLocalMap set方法
        else
            createMap(t, value);//否则创建ThreadLocalMap并set
    }
ThreadLocalMap::set
    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);

            /*
            寻找当前ThreadLocal是否已经设置过value
             */
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                //Entry获得的ThreadLocal等于当前ThreadLocal
                if (k == key) {
                    e.value = value;
                    return;
                }

                /*
                Entry里面的ThreadLocal为null

                有可能是ThreadLocal已经没有被强引用指向,gc已经把它回收了
                 */
                if (k == null) {
                    //替换旧的Entry,key为当前ThreadLocal
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            /*
           清理ThreadLocal为null的Entry,value指向null,防止memory leak
             */
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
ThreadLocal::createMap
 void createMap(Thread t, T firstValue) {
        // 初始化ThreadLocalMap,并把firstValue设置到Map中
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  1. 获取当前线程以及当前线程对象指向的ThreadLocalMap
  2. 如果ThreadLocalMap不为null,调用第3步 ThreadLocalMap的set方法。否则调用第4部 createMap
  3. ThreadLocalMap的set方法
  1. createMap,为当前线程创建一个ThreadLocalMap,并将当前需要存放的值存储进去

get()

ThreadLocal::get
 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//获取线程中的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T) e.value;
                return result;
            }
        }
        /*
        如果当前线程的ThreadLocal的Entry为null,
        或者ThreadLocalMap未初始化,
        就调用setInitialValue,返回一个初始值
         */
        return setInitialValue();
    }
    }
ThreadLocalMap::getEntry
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);
        }

1.根据当前线程获取ThreadLocalMap,如果map为null,调用 第2步,否则调用第3步
2.调用setInitialValue返回一个初始值
3.调用ThreadLocalMap的getEntry方法获取对应的值

ThreadLocal内存泄漏问题

image.png image.png
private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);//清除key为null的Entry
                }
            } while ((n >>>= 1) != 0);
            return removed;
        }
  private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

总结

ThreadLocal的用法相对简单,但由于有可能出现内存泄漏的情况,所以开发过程中需要注意。

上一篇下一篇

猜你喜欢

热点阅读