jdk1.8 ThreadLocal parse (use + + source code reading application scenarios + Notes)

We all already know the value of threadlocal thread can be private, and to take into their own data to protect the threads when multiple threads, similar to many articles, so this is no longer about the role of threadlocal, in addition to this article there is a beginner speak source relatively easy to overlook the place
tips: lengthy article, have the right sidebar directory options

use

public class Test {

    private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

    private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        stringThreadLocal.set("abc");
        integerThreadLocal.set(123);
        System.out.println(stringThreadLocal.get());
        System.out.println(integerThreadLocal.get());
    }
}

Yes, the above code is a beginner relatively easy to overlook the place, I was also quite puzzled that a thread can have multiple threadlocal it, and what type of threadlocal ask for it, you can have multiple correct answer is , but it may be a variety of types, as shown in the upper code.

Source interpretation (see also note comments)

Look at the diagram, you can read the source code and then turn back to think about
Here Insert Picture Description

ThreadLocal set()方法

Since the first method uses a set threadlocal, it would first set methods threadlocal class look at this is to manually set the time need to use the value threadlocal

public void set(T value) {
        Thread t = Thread.currentThread();  //获取当前线程
        ThreadLocalMap map = getMap(t);     //获取当前线程的threadlocalmap
        if (map != null)
            map.set(this, value);			//map不为空,直接放值,没错,这个map的key就是当前的threadlocal实例对象,value便是你的值
        else
            createMap(t, value);			当前线程的threadlocalmap为空,则调用创建方法
    }

Source code is the set top, we go step by step, first look * getMap () * method

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;			//可以看到这个方法就是直接把当前线程的threadlocals变量拿过来
    }

We see the thread threadlocals how variables are defined (note that this code is below Thread class, are ThreadLocal other columns in the source code, do not be confused):

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

We see this type threadlcoals variable i is threadLocalMap on an inner class threadlocal in this category we look directly at the top inside the first source block map.set () Method:

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

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
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();
        }

We can see an entry array. And the entry is a key, value, it is similar to and will be understood ThreadLocalMap hashmap, the upper value is the Object code, this set can be understood as a hashmap method put () method, but where the key is an object threadlocal instance, value is value, a thread has a map, and the map can be stored in different types of values, key threadlocal instance of an object is defined in your code, you may be confused at this time, since threadlocal is a global variable that all instances of an object is not thread you are the same, that is key is the same, right, this is because each thread has its own threadlocalmap as their own property, these map thread private, so it is not a conflict, and this map can store different instances of threadlocal as a key, that is, the upper test code defines several threadlocal variables, they put the object type can be any subclass, beginners will be able to think about a good understanding.
Of course, if a serious look at it, come here already know about it, then the way to the last createMap method of the first block of code in the source column about it:

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

Directly create a map, key to threadlocal instance, value is the value assigned to the variable threadlocalmap the current thread.

ThreadLocal get()方法

get method is simply to get the value, here to write in comments of

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {				//如果我们这个线程调用过set方法后,这个map就不会为空,执行if里边内容,根据这个ThreadLocal的实例对象获取这个线程对应的值,其实这个步骤在setInitialValue方法中已经有了,这里的作用主要就是提前做一次判断,如果有值,就不再调用setInitialValue方法,提高效率
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();//如果我们没有执行过set()方法,那么就调用这个方法,下边再进行介绍
    }

ThreadLocal setInitialValue()方法

Posted Source: This method is private, instructions are for internal use

private T setInitialValue() {
        T value = initialValue();   //这里又调用了initialValue()方法,返回了一个值,下个方法介绍我们就看它,或者此处也可以直接跳过去看
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)			//又一次进行了判断,检查这个过程中是否已经插入过值,如果有,则进行覆盖
            map.set(this, value);
        else
            createMap(t, value);		//如果没有值,那么将初始化方法中的值放进来
        return value;
    }

ThreadLocal initialValue()方法

This method may be easy to overlook, before talking about this method, interspersed with the first use of it, below is a small sample:

/**
 * 利用 ThreadLocal 给每个线程分配自己的 dateFormat 对象
 * 不但保证了线程安全,还高效的利用了内存
 */
public class ThreadLocalNormalUsage05 {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            //提交任务
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage05().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {

        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT 开始计时
        Date date = new Date(1000 * seconds);
        //获取 SimpleDateFormat 对象
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new
            ThreadLocal<SimpleDateFormat>(){

        //创建一份 SimpleDateFormat 对象
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
}

We can see that in the statement creates a ThreadLocal inherited from ThreeadLocal anonymous class and override the initialValue methods, top posted operating results:
Here Insert Picture Description

initialValue()方法:
就是示例中我们初始化需要重写的方法,如果没有初始化就为空,从get方法中调用setInitialValue方法,setInitialValue调用initialValue方法我们可以知道,这个初始化方法只有调用get时才会执行这个方法(前提是该线程没有设置过值),然后将这个初始化值放到当前线程的threadLocals中,此时这个值才真正属于当前线程

protected T initialValue() {
        return null;
    }

ThreadLocal remove()方法

调用remove()方法,就会删除对应的Entry对象,可以避免内存泄漏,所以确定后边执行不会再用到对应的ThreadLocal的话,要调用remove()方法进行释放。

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

应用场景

1.每个线程需要一个独享对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random)

每个Thread内有自己的实例副本,不共享

比喻:教材只有一本,一起做笔记有线程安全问题。复印后没有问题,使用ThradLocal相当于复印了教材。

2.每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦

注意事项

1.内存泄漏

问题

内存泄露;某个对象不会再被使用,但是该对象的内存却无法被收回

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

        Entry(ThreadLocal<?> k, Object v) {
            //调用父类,父类是一个弱引用
            super(k);
            //强引用
            value = v;
        }
    }

强引用:当内存不足时触发GC,宁愿抛出OOM也不会回收强引用的内存

弱引用:触发GC后便会回收弱引用的内存
正常情况:
当Thread运行结束后,ThreadLocal中的value会被回收,因为没有任何强引用了
非正常情况:
当Thread一直在运行始终不结束,强引用就不会被回收,存在以下调用链 Thread–>ThreadLocalMap–>Entry(key为null)–>value因为调用链中的 value 和 Thread 存在强引用,所以value无法被回收,就有可能出现OOM。

JDK的设计已经考虑到了这个问题,所以在set()、remove()、resize()方法中会扫描到key为null的Entry,并且把对应的value设置为null,这样value对象就可以被回收。

但是只有在调用set()、remove()、resize()这些方法时才会进行这些操作,如果没有调用这些方法并且线程不停止,那么调用链就会一直存在,所以可能会发生内存泄漏。

注意:这里说的这几个方法是内部类ThreadLocalMap中的
Here Insert Picture Description

private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            //当ThreadLocal为空时,将ThreadLocal对应的value也设置为null
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    setThreshold(newLen);
    size = count;
    table = newTab;
}

如何避免内存泄漏(阿里规约)

调用remove()方法,就会删除对应的Entry对象,可以避免内存泄漏,所以使用完ThreadLocal后,要调用remove()方法。

2.ThreadLocal的空指针异常问题

问题

/**
 * ThreadLocal的空指针异常问题
 */
public class ThreadLocalNPE {

    ThreadLocal<Long> longThreadLocal = new ThreadLocal<>();

    public void set() {
        longThreadLocal.set(Thread.currentThread().getId());
    }

    public Long get() {
        return longThreadLocal.get();
    }

    public static void main(String[] args) {

        ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE();

        //如果get方法返回值为基本类型,则会报空指针异常,如果是包装类型就不会出错
        System.out.println(threadLocalNPE.get());

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocalNPE.set();
                System.out.println(threadLocalNPE.get());
            }
        });
        thread1.start();
    }
}

解决方法

如果get方法返回值为基本类型,则会报空指针异常,如果是包装类型就不会出错。这是因为基本类型和包装类型存在装箱和拆箱的关系,造成空指针问题的原因在于使用者。

3.共享对象问题

如果在每个线程中ThreadLocal.set()进去的东西本来就是多个线程共享的同一对象,比如static对象,那么多个线程调用ThreadLocal.get()获取的内容还是同一个对象,还是会发生线程安全问题。

4.可以不使用ThreadLocal就不要强行使用

如果在任务数很少的时候,在局部方法中创建对象就可以解决问题,这样就不需要使用ThreadLocal。

5.优先使用框架的支持,而不是自己创造

例如在Spring框架中,如果可以使用RequestContextHolder,那么就不需要自己维护ThreadLocal,因为自己可能会忘记调用remove()方法等,造成内存泄漏。

Here Insert Picture Description

发布了43 篇原创文章 · 获赞 12 · 访问量 4663

Guess you like

Origin blog.csdn.net/Jarbein/article/details/103479593