• ThreadLocal认识


    1. 使用

    public class Main {
        public static void main(String[] args) {
            ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
            ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    threadLocalOne.set("Thread-1: --- threadLocal1 ");
                    threadLocalTwo.set("Thread-1: --- threadLocal2 ");
                    System.out.println(threadLocal1.get());
                    System.out.println(threadLocal2.get());
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(threadLocal1.get());
                    System.out.println(threadLocal2.get());
                    threadLocalOne.set("Thread-2: --- threadLocal1 ");
                    threadLocalTwo.set("Thread-2: --- threadLocal2 ");
                    System.out.println(threadLocalOne.get());
                    System.out.println(threadLocalTwo.get());
                }
            }).start();
        }
    }
    /*
    Thread-1: --- threadLocal1
    Thread-1: --- threadLocal2
    null
    null
    Thread-2: --- threadLocal1
    Thread-2: --- threadLocal2
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    简单易用,而且还实现了线程隔离;

    2. 线程隔离

    使用ThreadLocal在不同的线程中进行写操作,实际上数据都是绑定在当前线程的实例上,ThreadLocal.只负责读写操作,并不负责保存数据,这就解释了,为什么ThreadLocal.set数据,只在操作的线程中有用;

    // ThreadLocal.java
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocal.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();
    }
    
    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    //Thread.java
    public class Thread implements Runnable {
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    上述代码就实现了线程隔离,将ThreadLocalMapThread一对一绑定,而ThreadLoal仅仅是作为ThreadLocalMap对外的管理员而已,

    3. Entry

    这是一个弱引用,以ThreadLocal作为key,以需要存入的对象 T为value。这里需要强调一点的是虽然这是一个弱引用,但是对于ThreadLocal来说,它自己要产生对象是new出来的,所以对于ThreadLocal自己肯定是强引用;
    一个线程可能存在多个ThreadLocal,但是ThreadLocalMap只有一份。getset方法针对当前线程操作目标对象,每次执行都会先将之前的Entry做一次回收,key为弱引用的目的是当线程执行完毕后,若下次有GC发生,那么线程执行完毕后的该Entry对象可以自行回收掉。

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Entry的key引用被gc回收,需要先手动断开ThreadLocal本身的强引用

    static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(()->{
            threadLocal1.set(new Object());
            try {
                threadLocal1 = null;
                System.gc();
    
                //下面代码来自:https://blog.csdn.net/thewindkee/article/details/103726942
                Thread t = Thread.currentThread();
                Class<? extends Thread> clz = t.getClass();
                Field field = clz.getDeclaredField("threadLocals");
                field.setAccessible(true);
                Object threadLocalMap = field.get(t);
                Class<?> tlmClass = threadLocalMap.getClass();
                Field tableField = tlmClass.getDeclaredField("table");
                tableField.setAccessible(true);
    
                Object[] table = (Object[]) tableField.get(threadLocalMap);
                for (Object entry : table) {
                    if (entry == null) continue;
                    Class<?> entryClass = entry.getClass();
                    Field valueField = entryClass.getDeclaredField("value");
                    Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
                    valueField.setAccessible(true);
                    referenceField.setAccessible(true);
                    System.out.printf("弱引用key:%s    值:%s%n", referenceField.get(entry), valueField.get(entry));
                }
            } catch (Exception e) { }
        }).start();
    }
    /*
    注:多出来的两个,说明该线程中还有另外两个ThreadLocal,反正看不见,也不影响当前ThreadLocal使用,不管了
    弱引用key:null    值:java.lang.Object@54da927
    弱引用key:jdk.internal.jimage.ImageBufferCache$1@431b1659    值:[Ljdk.internal.jimage.ImageBufferCache$BufferReference;@5690d712
    弱引用key:java.lang.ThreadLocal@258bbd70    值:java.lang.ref.SoftReference@4d673c77
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    4. ThreadLocalMap

    维护了一个Entry数组table,它比较核心一点的地方是,自身是一个环形结构,nextIndex方法,将传入的index+1,当index长度超过数组长度后,会直接返回0,又回到数组头部,这就完成一个环形结构

    public class ThreadLocal<T> {
        private Entry[] table;
        ...
        static class ThreadLocalMap {
            ...
            private static int nextIndex(int i, int len) {
                return ((i + 1 < len) ? i + 1 : 0);
            }
            ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5. 内存泄漏

    ThreadLocalsetgetremove方法看下来,除了正常的功能之外,就是多了对key为null的entry的清理工作,方便GC回收这部分占用的内存。expungeStaleEntry就是最核心的清理方法,这也是ThreadLocalMap的一种防范机制,因为ThreadLocalMap的生命周期和线程是一样长。内存泄露就是创建了太多的ThreadLocal变量,然后呢,又没有及时的释放内存;内存溢出可以理解为创建了多个ThreadLocal变量,然后又给它们分配了占用内存比较大的对象,使得多个线程累计占用太多内存,导致系统出现内存溢出。

    remove方法主要是为了防止内存溢出和内存泄露,使用的时机一般是在线程运行结束之后使用,也就是run()方法结束之后。

  • 相关阅读:
    iPhone 14 Pro/Max出大问题:从旧款机型传输数据后卡死黑屏无法开机!
    面试:TCP UDP 区别
    Java面试之Redis篇(offer 拿来吧你)
    【电源专题】什么是AC/DC转换器
    一个最简单的自定义锁屏应用实现
    Logstash介绍
    速度提升1.25倍,Python 3.11 Beta版本的首个预览版本发布
    php以半小时为单位,输出指定的时间范围
    【JavaSE】多线程篇(三)用户线程、守护线程与线程的7大状态
    【云原生】springcloud12——服务网关Gateway
  • 原文地址:https://blog.csdn.net/qq_37776700/article/details/126864422