Java中的ThreadLocal线程池原理

 更新时间:2023年11月20日 10:58:28   作者:♛薄情痞子♛  
这篇文章主要介绍了Java中的ThreadLocal线程池原理,ThreadLocal提供了线程的局部变量(或本地变量),它可以保证访问到的变量属于当前线程,每个访问这种变量的线程(通过它的get或set方法)都有自己的、独立初始化的变量副本,需要的朋友可以参考下

ThreadLocal

ThreadLocal提供了线程的局部变量(或本地变量)。

它可以保证访问到的变量属于当前线程,每个访问这种变量的线程(通过它的get或set方法)都有自己的、独立初始化的变量副本,每个线程的变量都不同。

ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。

ThreadLocal类定义如下:可以简单瞄一眼吆,毕竟没有浏览的欲望....

public class ThreadLocal<T> {
   
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
 
    protected T initialValue() {
        return null;
    }
 
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
 
    public ThreadLocal() {
    }
 
    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();
    }
 
    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;
    }
 
    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 void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
 
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
 
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }
 
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
 
        private final Supplier<? extends T> supplier;
 
        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }
 
        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }
 
    static class ThreadLocalMap {
 
        static class Entry extends WeakReference<ThreadLocal<?>> {
         
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
 
       
        private static final int INITIAL_CAPACITY = 16;
 
        
        private Entry[] table;
 
       
        private int size = 0;
 
       
        private int threshold; // Default to 0
 
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
 
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
 
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
 
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
 
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
 
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
 
        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);
        }
 
        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;
        }
 
        private void set(ThreadLocal<?> key, Object value) {
 
            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();
        }
 
       
        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;
                }
            }
        }
 
       
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;
 
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
 
                if (k == key) {
                    e.value = value;
 
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;
 
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
 
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }
 
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
 
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
 
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
 
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;
 
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;
 
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
 
        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);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }
 
        private void rehash() {
            expungeStaleEntries();
 
            if (size >= threshold - threshold / 4)
                resize();
        }
 
        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();
                    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;
        }
 
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
}

 内部方法:

ThreadLocal通过threadLocalHashCode来标识每一个ThreadLocal的唯一性。threadLocalHashCode通过CAS操作进行更新,每次hash操作的增量为 0x61c88647(不知为何)。

接下来看下ThreadLocal的set、get等相关主要方法

set方法:

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

通过Thread.currentThread()方法获取了当前的线程引用,并传给了getMap(Thread)方法获取一个ThreadLocalMap的实例。我们继续跟进getMap(Thread)方法:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到getMap(Thread)方法直接返回Thread实例的成员变量threadLocals。它的定义在Thread内部,访问级别为package级别:

public class Thread implements Runnable {
    private static native void registerNatives();
    static {
        registerNatives();
    }
 
    private volatile char  name[];
    private int            priority;
    private Thread         threadQ;
    private long           eetop;
    private boolean     single_step;
    private boolean     daemon = false;
    private boolean     stillborn = false;
    private Runnable target;
    private ThreadGroup group;
    private ClassLoader contextClassLoader;
    private AccessControlContext inheritedAccessControlContext;
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
 
    ThreadLocal.ThreadLocalMap threadLocals = null;
 
    ..........
 
}

到了这里,我们可以看出,每个Thread里面都有一个ThreadLocal.ThreadLocalMap成员变量,也就是说每个线程通过ThreadLocal.ThreadLocalMap与ThreadLocal相绑定,这样可以确保每个线程访问到的thread-local variable都是本线程的。

我们往下继续分析。获取了ThreadLocalMap实例以后,如果它不为空则调用ThreadLocalMap.ThreadLocalMap 的set方法设值;若为空则调用ThreadLocal 的createMap方法new一个ThreadLocalMap实例并赋给Thread.threadLocals。

ThreadLocal 的 createMap方法的源码如下:

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

而ThreadLocalMap是ThreadLocal的一个静态内部类,在文章开头贴出的ThreadLocal源码可查看。

总结:

set操作是向当前线程的ThreadLocal.ThreadLocalMap类型的成员变量threadLocals中设置值,key是this,value是我们指定的值

注意,这里传的this代表的是那个ThreadLocal类型的变量(或者说叫对象)

也就是说,每个线程都维护了一个ThreadLocal.ThreadLocalMap类型的对象,而set操作其实就是以ThreadLocal变量为key,以我们指定的值为value,最后将这个键值对封装成Entry对象放到该线程的ThreadLocal.ThreadLocalMap对象中。每个ThreadLocal变量在该线程中都是ThreadLocal.ThreadLocalMap对象中的一个Entry。既然每个ThreadLocal变量都对应ThreadLocal.ThreadLocalMap中的一个元素,那么就可以对这些元素进行读写删除操作。

get方法

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();
    }

通过Thread.currentThread()方法获取了当前的线程引用,并传给了getMap(Thread)方法获取一个ThreadLocalMap的实例,getMap方法前面已经贴出来了。继续跟进setInitialValue()方法:

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;
    }

首先调用 initialValue()方法来初始化,然后 通过Thread.currentThread()方法获取了当前的线程引用,并传给了getMap(Thread)方法获取一个ThreadLocalMap的实例,并将 初始化值存到ThreadLocalMap 中。

initialValue() 源码如下:

protected T initialValue() {
        return null;
    }

总结:

get()方法就是从当前线程的ThreadLocal.ThreadLocalMap对象中取出对应的ThreadLocal变量所对应的值

同理,remove()方法就是清除这个值

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

ThreadLocal的图形理解:

 或者

 ThreadLocal的使用场景是在线程的声明周期内传值(数据库连接、session管理等),ThreadLocal关键点是在于ThreadLocalMap,可以说一切归功于此,看完上面的描述,应该会有一个直观的体会吧。

下面我们探究一下ThreadLocalMap的实现。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的静态内部类,部分源码如下:

public class ThreadLocal<T> {
 
    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;
            }
        }
 
        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
 
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
 
        /**
         * The number of entries in the table.
         */
        private int size = 0;
 
        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0
 
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
    }
    
}

其中INITIAL_CAPACITY代表这个Map的初始容量;1是一个Entry类型的数组,用于存储数据;size代表表中的存储数目;threshold代表需要扩容时对应size的阈值。

Entry类是ThreadLocalMap的静态内部类,用于存储数据。

Entry类继承了WeakReference<ThreadLocal<?>>,即每个Entry对象都有一个ThreadLocal的弱引用(作为key),这是为了防止内存泄露。一旦线程结束,key变为一个不可达的对象,这个Entry就可以被GC了。

接下来我们来看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);
 
            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 的get方法会调用 ThreadLocalMap 的 getEntry(ThreadLocal key) ,其源码如下:

 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);
    }
 
    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弱引用

说ThreadLocal是一个弱引用,其本质是ThreadLocal类中ThreadLocalMap类中的Entry的key是一个弱引用。前面提到过Entry中的key是this,this指向ThreadLocal

在ThreadLocal源码中,截取一段ThreadLocalMap的源码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                //由于Entry继承了WeakReference,所以这里以一个弱引用指向ThreadLcoal对象
                super(k);
                value = v;
            }
        }

为什么要这么做呢?

看下面的这种场景:

public void func1() {
        ThreadLocal tl = new ThreadLocal<Integer>(); //line1
         tl.set(100);   //line2
         tl.get();       //line3
}

line1新建了一个ThreadLocal对象,t1 是强引用指向这个对象;line2调用set()后,新建一个Entry,通过源码可知entry对象里的 k是弱引用指向这个对象。如图:

当func1方法执行完毕后,栈帧销毁,强引用 tl 也就没有了,但此时线程的ThreadLocalMap里某个entry的 k 引用还指向这个对象。若这个k 引用是强引用,就会导致k指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏,但是弱引用就不会有这个问题(弱引用及强引用等这里不说了)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收,而且在entry的k引用为null后,再调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

概括说就是:在方法中新建一个ThreadLocal对象,就有一个强引用指向它,在调用set()后,线程的ThreadLocalMap对象里的Entry对象又有一个引用 k 指向它。如果后面这个引用 k 是强引用就会使方法执行完,栈帧中的强引用销毁了,对象还不能回收,造成严重的内存泄露。

注意:虽然弱引用,保证了k指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现k为null时才会去回收整个entry、value,因此弱引用不能保证内存完全不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

到此这篇关于Java中的ThreadLocal线程池原理的文章就介绍到这了,更多相关ThreadLocal线程池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于Failed to load ApplicationContext异常的解决思路

    基于Failed to load ApplicationContext异常的解决思路

    这篇文章主要介绍了基于Failed to load ApplicationContext异常的解决思路,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • MyBatis批量插入(insert)数据操作

    MyBatis批量插入(insert)数据操作

    本文给大家分享MyBatis批量插入(insert)数据操作知识,非常不错,具有参考借鉴价值,感兴趣的朋友一起学习吧
    2016-06-06
  • Idea如何导入一个SpringBoot项目的方法(图文教程)

    Idea如何导入一个SpringBoot项目的方法(图文教程)

    这篇文章主要介绍了Idea如何导入一个SpringBoot项目的方法(图文教程),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 详解springboot的三种启动方式

    详解springboot的三种启动方式

    这篇文章主要介绍了详解springboot的三种启动方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • 基于google zxing的Java二维码生成与解码

    基于google zxing的Java二维码生成与解码

    这篇文章主要为大家详细介绍了基于google zxing的Java二维码生成与解码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • java线程池参数自定义设置详解

    java线程池参数自定义设置详解

    这篇文章主要为大家介绍了java线程池参数自定义设置详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • PageHelper引发的幽灵数据问题解析

    PageHelper引发的幽灵数据问题解析

    这篇文章主要为大家介绍了PageHelper引发的幽灵数据问题解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Java代理的几种实现方式总结

    Java代理的几种实现方式总结

    本文将通过例子说明java代理的几种实现方式,并比较它们之间的差异,文中通过代码示例给大家介绍的非常详细,对大家的学习或工作有一定的参考价值,需要的朋友可以参考下
    2023-12-12
  • Java Clone(类的复制)实例代码

    Java Clone(类的复制)实例代码

    Java Clone(类的复制)实例代码,需要的朋友可以参考一下
    2013-03-03
  • Java ArrayList与LinkedList及HashMap容器的用法区别

    Java ArrayList与LinkedList及HashMap容器的用法区别

    这篇文章主要介绍了Java ArrayList与LinkedList及HashMap容器的用法区别,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-07-07

最新评论