zoukankan      html  css  js  c++  java
  • ThreadLocal的原理

    ThreadLocal是一个支持泛型的java类,抛开里面的静态内部类ThreadLocalMap不说,其实它没几行代码,不信,您自己去看看。它用来干啥?类上注释说的很明白:

    • 它能让线程拥有了自己内部独享的变量

    • 每一个线程可以通过get、set方法去进行操作

    • 可以覆盖initialValue方法指定线程独享的值

    • 通常会用来修饰类里private static final的属性,为线程设置一些状态信息,例如user ID或者Transaction ID

    • 每一个线程都有一个指向threadLocal实例的弱引用,只要线程一直存活或者该threadLocal实例能被访问到,都不会被垃圾回收清理掉。

    话不多说,我们来看get方法内部实现:

    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();
        }
    • 获取当前线程内部的ThreadLocalMap

    • map存在则获取当前ThreadLocal对应的value值

    • map不存在或者找不到value值,则调用setInitialValue,进行初始化

    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方法,获取初始化值【调用者通过覆盖该方法,设置自己的初始化值】

    • 获取当前线程内部的ThreadLocalMap

    • map存在则把当前ThreadLocal和value添加到map中

    • map不存在则创建一个ThreadLocalMap,保存到当前线程内部

    小结

    每一个线程都有一个私有变量,是ThreadLocalMap类型。当为线程添加ThreadLocal对象时,就是保存到这个map中,所以线程与线程间不会互相干扰。总结起来,一句话:我有我的map。

    神奇的remove

    因为ThreadLocal使用不当,会引发内存泄露的问题

    示例一:
    public class MemoryLeak {
     
         public static void main(String[] args) {
             new Thread(new Runnable() {
                 @Override
                 public void run() {
                     for (int i = 0; i < 1000; i++) {
                         TestClass t = new TestClass(i);
                         t.printId();
                        t = null;
                    }
               }
            }).start();
        }
    
        static class TestClass{
            private int id;
            private int[] arr;
            private ThreadLocal<TestClass> threadLocal;
            TestClass(int id){
                this.id = id;
                arr = new int[1000000];
                threadLocal = new ThreadLocal<>();
                threadLocal.set(this);
            }
    
            public void printId(){
                System.out.println(threadLocal.get().id);
            }
        }
    }

    运行结果:

    0
    1
    2
    3
    5...省略...
    440
    441
    442
    443
    444
    Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at com.gentlemanqc.MemoryLeak$TestClass.<init>(MemoryLeak.java:33)
        at com.gentlemanqc.MemoryLeak$1.run(MemoryLeak.java:16)
        at java.lang.Thread.run(Thread.java:745)

    对上述代码稍作修改,请看:

    public class MemoryLeak {
     
         public static void main(String[] args) {
             new Thread(new Runnable() {
                 @Override
                 public void run() {
                     for (int i = 0; i < 1000; i++) {
                         TestClass t = new TestClass(i);
                         t.printId();
                         t.threadLocal.remove();
                    }
               }
            }).start();
        }
    
        static class TestClass{
            private int id;
            private int[] arr;
            private ThreadLocal<TestClass> threadLocal;
            TestClass(int id){
                this.id = id;
                arr = new int[1000000];
                threadLocal = new ThreadLocal<>();
                threadLocal.set(this);
            }
    
            public void printId(){
                System.out.println(threadLocal.get().id);
            }
        }
    }

    运行结果:

    0
    1
    2
    3
    ...省略...
    996
    997
    998
    999

    一个内存泄漏,一个正常完成,对比代码只有一处不同:t = null改为了t.threadLocal.remove();哇,神奇的remove!!!

    set(T 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);
        }
    • 获取当前线程内部的ThreadLocalMap

    • map存在则把当前ThreadLocal和value添加到map中

    • map不存在则创建一个ThreadLocalMap,保存到当前线程内部

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

    就一句话,获取当前线程内部的ThreadLocalMap,存在则从map中删除这个ThreadLocal对象。

    无处不在的ThreadLocalMap

    • ThreadLocalMap是一个自定义的hash map,专门用来保存线程的thread local变量

    • 它的操作仅限于ThreadLocal类中,不对外暴露

    • 这个类被用在Thread类的私有变量threadLocals和inheritableThreadLocals上

    • 为了能够保存大量且存活时间较长的threadLocal实例,hash table entries采用了WeakReferences作为key的类型

    • 一旦hash table运行空间不足时,key为null的entry就会被清理掉

    当创建一个ThreadLocalMap时,实际上内部是构建了一个Entry类型的数组,初始化大小为16,阈值threshold为数组长度的2/3,Entry类型为WeakReference,有一个弱引用指向ThreadLocal对象。

    为什么Entry采用WeakReference类型?

    Java垃圾回收时,看一个对象需不需要回收,就是看这个对象是否可达。什么是可达,就是能不能通过引用去访问到这个对象。(当然,垃圾回收的策略远比这个复杂,这里为了便于理解,简单给大家说一下)。

    jdk1.2以后,引用就被分为四种类型:强引用、弱引用、软引用和虚引用。强引用就是我们常用的Object obj = new Object(),obj就是一个强引用,指向了对象内存空间。当内存空间不足时,Java垃圾回收程序发现对象有一个强引用,宁愿抛出OutofMemory错误,也不会去回收一个强引用的内存空间。而弱引用,即WeakReference,意思就是当一个对象只有弱引用指向它时,垃圾回收器不管当前内存是否足够,都会进行回收。反过来说,这个对象是否要被垃圾回收掉,取决于是否有强引用指向。ThreadLocalMap这么做,是不想因为自己存储了ThreadLocal对象,而影响到它的垃圾回收,而是把这个主动权完全交给了调用方,一旦调用方不想使用,设置ThreadLocal对象为null,内存就可以被回收掉。

    注意:在使用完之后,最好手动调用threadlocal的remove()方法清理掉应用,防止内存泄漏。

  • 相关阅读:
    Jquery的函数
    Jquery的$.ajax()函数$.get(),$.post(),$.getjson(),$.ajax()
    $.ajax()详解
    JSON 数据格式,能比较清晰容易理解的解读json
    练习:WinForm 对话框控件(文件读取、写入)
    练习:WinForm 对话框控件(显示、获取)
    练习:WinForm--DataGridView增删改查完整版
    Chapter 10. WinForm-DataGridView(确认删除、多条件查询、数据区别显示)
    Chapter 10. WinForm-DataGridView
    练习:WinForm 计算器
  • 原文地址:https://www.cnblogs.com/cq-yangzhou/p/11327197.html
Copyright © 2011-2022 走看看