人手一支笔:ThreadLocal 实现原理与源码详解

  • 在保证线程安全时,我们可以对共享资源加锁来控制线程对资源的访问。这是第一种方式:加锁。除此之外,我们还可以通过增加资源来保证所有对象的线程安全,这就是第二种方式:ThreadLocal

  • 举个例子:比如,让100 个人填写个人信息表,如果只有一支笔,那么大家就得挨个填写,对于管理人员来
    说,必须保证大家不会去哄抢这仅存的一支笔,否则,谁也填不完。从另外一个角度出发,我们可以准备100支笔,人手一支,那么所有人很快就能完成表格的填写工作。上面例子中锁使用的是第一种思路,ThreadLocal使用的就是第二种思路。

  • 一个Thread有着自己对应的一个ThreadLocalMap。而ThreadLocal与Thread是多对多的关系,ThreadLocal变量作为ThreadLocalMap中的键,我们要放的数据(实例)就是ThreadLocalMap中的值

  • 以下源码均基于jdk1.8。

一、ThreadLocal是什么

从ThreadLocal名字上看,它是线程的一个局部变量。也就是说,只有当前线程可以访问,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,只有当前线程可以访问的数据,自然是线程安全的。

ThreadLocal类会根据当前线程存储变量数据的副本,每一个线程之间数据副本互不干扰,实现线程之间的数据隔离。(ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。)

ThreadLocal的使用场景:

  • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  • 线程间数据隔离
  • 进行事务操作,用于存储线程事务信息。
  • 数据库连接Connection、Session会话管理。
  • 总的来说就是每个线程需要有自己的实例(数据,我们放的值),这些实例需要在多个方法中(一个线程中)共享,但不希望被多个线程共享

ThreadLocal 如何做到为每个线程存有一份独立的本地值呢?

  • 一个 ThreadLocal 实例可以形象地理解为一个 Map(早期版本的 ThreadLocal 是这样设计的)。
  • 当工作线程 Thread 实例向本地变量保持某个值时,会以 “Key-Value对”(即键-值对)的形式保存在 ThreadLocal 内部的Map中,其中 Key 为线程 ThreadLocal 实例,Value 为待保存的值。
  • 当工作线程 Thread 实例从 ThreadLocal 本地变量取值时,会以 ThreadLocal 实例为 Key,获取其绑定的 Value。
  • 一个ThreadLocal实例内部结构的形象如下所示:
    在这里插入图片描述

二、ThreadLocal怎么用

既然ThreadLocal的作用是为每一个线程创建一个副本,我们使用一个例子来验证一下:

  • 通过代码的输出,我们可以看到,对每一个线程来说,都有着同一个ThreadLocal(这也就是每个 Thread 都拥有自己的 ThradLocal 副本),这也是因为这些线程都是用的同一个threadLocal的set方法。
  • 在之后分析源码我们就会知道,每一个Thread都有一个属于自己的ThreadLocalMap,这个map中的键是ThreadLocal,值就是我们自己set时设置的值。ThreadLocal 获取值时实际上是从当前 Thread 的map中获取(以自己为key),这也就是为什么 ThreadLocal 能在每个 Thread 中保持一个副本,实际上数据是放在 Thread 中的。
  • 线程与线程之间是隔离的,虽然它们的ThreadLocal是相同的,但是ThreadLocal仅仅是作为Map中的键,之后分析源码可知,在调用get()方法取值时,就是通过这个threadLocal作为键来取值。当然我们也可以创建多个ThreadLocal用来调用set()方法,这样每一个Thread对应的ThreadLocalMap中就有了多条以不同的ThreadLocal为键的键值对数据。(然而这个操作是没什么意义的,仅仅能证明Thread与ThreadLocal之间是多对多的关系,我们在用ThreadLocal时,用一个就可以了,例如我们使用数据库的时候首先就是建立数据库连接,如果有1个客户端频繁的使用数据库,那么就需要建立多次链接和关闭,我们的服务器可能会吃不消,怎么办呢?如果有一万个客户端,那么服务器压力更大。这时候最好ThreadLocal,因为ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。而这些用一个ThreadLocal就可以实现了。这个也是案例二,代码在下面)
//案例一
package com.wlw.ThreadLocalDemo;

import java.util.Random;

public class TLDemo01 {
    public static void main(String[] args) {

        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                threadLocal.set(" "+random.nextInt(10));
                System.out.println("当前线程:"+Thread.currentThread().getName()+",当前ThreadLocal:"+threadLocal.hashCode()+",当前线程对应的ThreadLocalMap里存的值:"+threadLocal.get());
                
                 //threadLocal2.set(" "+random.nextInt(100));
                //System.out.println("当前线程:"+Thread.currentThread().getName()+",当前ThreadLocal:"+threadLocal2.hashCode()+",当前线程对应的ThreadLocalMap里存的值:"+threadLocal2.get());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
/*
当前线程:Thread-0,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 5
当前线程:Thread-1,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 7
当前线程:Thread-2,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 1
当前线程:Thread-3,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 4
当前线程:Thread-4,当前ThreadLocal:1286344437,当前线程对应的ThreadLocalMap里存的值: 2
*/
//案例二:用ThreadLocal来保存一个线程的数据库连接Connection
package com.wlw.chapter1_JDBC.demo04_person.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class DBUtils {
    private static final Properties properties = new Properties();
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    static {
        InputStream is = DBUtils.class.getResourceAsStream("/db.properties");
        try {
            properties.load(is);
            Class.forName(properties.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection(){
        Connection connection = threadLocal.get();//将当前线程中绑定的Connection对象,赋值给connection
        try {
            if(connection == null){
                connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("username"), properties.getProperty("password"));
                threadLocal.set(connection);//把第一次获得的连接 存在当前线程中
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    //开启事务
    public static void begin(){
        try {
            Connection connection = getConnection();
            connection.setAutoCommit(false);//设置手动提交
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //提交事务
    public static void commit(){
        Connection connection = null;
        try {
            connection = getConnection();
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            CloseAll(connection,null,null);
        }
    }
    //回滚事务
    public static void rollback(){
        Connection connection = null;
        try {
            connection = getConnection();
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            CloseAll(connection,null,null);
        }
    }

    public static void CloseAll(Connection connection, Statement statement, ResultSet resultSet){
        try {
            if(resultSet != null){
                resultSet.close();
            }
            if(statement !=null){
                statement.close();
            }
            if(connection != null){
                connection.close();
                threadLocal.remove();//关闭连接后,移除已关闭的Connection对象
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

三、ThreadLocal源码分析

在看ThreadLocal源码时,我们要从最常用的两个方法入手,上面的案例也有用到,即set()和get()方法,另外还有两个方法需要需要知道:remove()和initialValue()方法

在这里插入图片描述

3.1 set()方法

// java/lang/ThreadLocal.java 
	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
	 /**
       * Set the value associated with key.
       *
       * @param key the thread local object
       * @param value the value to be 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();
      }
        
       
	void createMap(Thread t, T firstValue) {
	    t.threadLocals = new ThreadLocalMap(this, firstValue);
	}
  • 从set方法我们可以看到,首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前threadlocal作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。
3.1.1 ThreadLocalMap、getMap()
  • 那ThreadLocalMap是什么,getMap方法又是如何实现的?
// java/lang/ThreadLocal.java 
	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;
            }
        }
        //........很多很多
    }
  • 我们可以看到ThreadLocalMap其实就是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。每个 Entry 都是一个对 的弱引用,这一点从super(k)可看出。另外,每个 Entry 都包含了一个对 的强引用。(这一点可能会引起内存泄漏,具体可看下面第四部分)
  • 下面来看一下getMap()方法
// java/lang/ThreadLocal.java   
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; //Ctrl+左键,点击threadLocals进入了Thread的源码中
    }
// java/lang/Thread.java
    ThreadLocal.ThreadLocalMap threadLocals = null;
  • 到这,我们清晰的知道了一个Thread都有一个属于自己的ThreadLocalMap用来存放本线程的数据,而ThreadLocalMap是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,使用ThreadLocal作为key,使用我们设置的value作为value

3.2 get()方法

// java/lang/ThreadLocal.java   
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();
    }
  • get方法同样是获取方法调用线程,之后也是通过getMap(t)方法获取ThreadLocalMap 的实例map。如果map!=null直接通过 ThreadLocalMap.Entry e = map.getEntry(this);以当前的threadlocal为键,获取ThreadLocalMap.Entry,返回其中的value数据。(ThreadLocalMap类将数据存储到其持有Entry对象的value属性中,Entry是其静态内部类,继承自WeakReference)如果map==null则会调用setInitialValue()方法(初始化方法)
3.2.1 setInitialValue()方法
// java/lang/ThreadLocal.java   

protected T initialValue() {
    return null;
}

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;
    }
  • setInitialValue()方法一般只调用一次(当调用ThreadLocal的remove方法后需要再次调用此方法来初始化数据),用于初始化数据,其首先调用initialValue()方法返回默认返回一个null,我们可以通过重写这个方法来指定初始化的默认值。后面的代码和上面相似,就是获取ThreadLocalMap将初始化的value存储到ThreadLocalMap中。如下面案例:
package com.wlw.ThreadLocalDemo;

import java.util.Random;

public class TLDemo01 {
    public static void main(String[] args) {

        ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
            @Override
            protected String initialValue() {
                return "我是默认值";
            }
        };

        System.out.println("主线程对应的ThreadLocalMap里存的值:"+threadLocal.get());
    }
}
/*
主线程对应的ThreadLocalMap里存的值:我是默认值
当前线程:Thread-0,当前ThreadLocal:1754195085,当前线程对应的ThreadLocalMap里存的值:hello
*/

3.3 remove()方法

// java/lang/ThreadLocal.java     
	public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
static class ThreadLocalMap {
 //.....
     
     	/**
         * Remove the entry for key.
         */
        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;
                }
            }
        }
    	//这个方法就是将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收
        private int expungeStaleEntry(int staleSlot) {
                Entry[] tab = table;
                int len = tab.length;

                // expunge entry at staleSlot
                tab[staleSlot].value = null;
                tab[staleSlot] = null;
                size--;

                // Rehash until we encounter null
                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;

                            // Unlike Knuth 6.4 Algorithm R, we must scan until
                            // null because multiple entries could have been stale.
                            while (tab[h] != null)
                                h = nextIndex(h, len);
                            tab[h] = e;
                        }
                    }
                }
                return i;
            }
// ..... 
}
  • remove()方法,就是将Map中的Entry设置为null,使其可以被回收。expungeStaleEntry()方法在下一部分ThreadLocalMap防止内存泄漏也有用到。

四、ThreadLocalMap与内存泄漏

4.1发生内存泄露的场景

ThreadLocalMap 是与线程关联的,它的生命周期与线程的生命周期一样长。如果线程长时间运行,并且 ThreadLocal 不再被使用,但 ThreadLocalMap 中的值仍然被强引用,那么ThreadLocalMap 里面的数据就会一直占用内存,导致内存泄漏。

这里再说一下数据结构吧。ThreadLocalMap 是ThreadLocal里的一个静态内部类,ThreadLocalMap 的键是 ThreadLocal 的弱引用,但值是强引用。这意味着,即使 ThreadLocal 实例本身被垃圾回收了,导致ThreadLocalMap 的键可能为null,但只要 ThreadLocalMap 还持有对值的强引用,不会被垃圾回收。

具体的场景可以想象下:线城池。线程池意味着当前线程未必会退出(比如固定大小的线程池,线程总是存在),这种情况,将一些大的对象设置到ThreadLocalMap中,可能会使系统出现内存泄漏的可能。

这里也可以简单看一下线程退出的方法源码:

public
class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     * 在线程退出前,由系统回调,进行资源清理
     * 将ThreadLocalMap 的实例对象置为null,使其可以被回收
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        // 这里将ThreadLocalMap置为null
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
 }

4.2发生内存泄露的根本原因

可以先说下结论:内存泄露的根本原因在于是否手动清除操作(ThreadLocal.remove()方法),而不是弱引用。

来看分析:
经过上面的介绍,你应该还记得在 ThreadLocalMap 中的 Entry 的 key 是对 ThreadLocal 的 WeakReference 弱引用,而 value 是强引用。注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个弱引用。再来看一下源码吧。

public class ThreadLocal<T> {
	 /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
	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;
            }
        }
        //........很多很多
    }
 }

这里思考一个问题:为什么使用弱引用而不是强引用?

这个问题在源码的注释上就有说明(看注释多么重要):To help deal withvery large and long-lived usages, the hash table entries use WeakReferences for keys(为了帮助处理非常大和长期的使用,哈希表条目使用WeakReferences作为键)

所以,弱引用反而是为了解决内存存储问题而专门使用的。

仔细思考一下,实际上,采用弱引用反而多了一层保障。如果ThreadLocal作为key使用强引用,那么只要ThreadLocal实例本身在内存中,它的entry(包括value)就会始终存在于ThreadLocalMap中,即使线程已经不再需要它的ThreadLocal变量。它们也不会被回收,这肯定会导致内存泄漏。

而如果我们使用WeakReference作为key。这意味着当对ThreadLocal实例的所有强引用都被垃圾收集器清除后,entry的键就会变为null,然后再通过expungeStaleEntry()replaceStaleEntry ()方法,将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收,防止了潜在的内存泄漏。

这样设计让ThreadLocal生命周期的控制权交给了用户,用户可以选择什么时候结束ThreadLocal实例的生命周期。ThreadLocal被清理后key为null,对应的value在下一次ThreadLocalMap调用set、get时清除,这样就算忘记调用 remove 方法,弱引用比强引用可以多一层保障。

所以,内存泄露的根本原因在于是否手动清除操作(ThreadLocal.remove()方法),而不是弱引用。

所以建议,在使用ThreadLocal时,在最后一定要使用ThreadLocal.remove()方法将这个变量移除,闭数据库连接一样,如果你确实不需要这个对象了,就应该告诉虚拟机,请把它回收,防止内存泄漏。
另外一种有趣的情况是JDK也可能允许你像释放普通变量一样释放ThreadLocal。比如,我们有时候为了加速垃圾回收,会特意写出类似 obj=null的代码。如果这么做,那么 obj所指向的对象就会更容易地被垃圾回收器发现,从而加速回收。同理,如果对于ThreadLocal的变量,我们也手动将其设置为null,那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收。(这种情况,代码中并没有调用threadLocal.remove()去移除线程的局部变量,但是系统依然有可能回收它们)。

4.3 ThreadLocal的回收机制 (清理机制:探测式清理和启发式清理)

其实在4.2小节也基本把回收机制的核心说过了,我们再来详细看看吧。

ThreadLocalMap中的 Entry 的 key 是对 ThreadLocal 的 WeakReference 弱引用(因为是弱引用,它并不真的持有ThreadLocal的引用。而当ThreadLocal的外部强引用被回收时,ThreadLocalMap中的key就会变成null),而 value 是强引用。

  • 所以ThreadLocal在使用过程中,只要存在强引用指向ThreadLocal实例,它就不会被回收。只有当没有任何强引用指向ThreadLocal实例时,它才可能被垃圾收集器回收,此时对应的映射项也会被清理,避免了内存泄漏的问题。在正常的使用场景下,不需要担心ThreadLocal在使用过程中变为null。

探测式清理

当线程调用 ThreadLocal 的 get()、set() 或 remove() 方法时,会触发对 ThreadLocalMap 的清理。此时,ThreadLocalMap 会检查所有键(ThreadLocal 实例),并移除那些已经被垃圾回收的键及其对应的值。这种清理是主动的,因为它是在每次操作 ThreadLocal 时进行的。

说白了就是:从当前节点开始遍历数组,将key等于null的entry置为null,key不等于null则rehash重新分配位置,若重新分配上的位置有元素则往后顺延。

启发式清理

在 ThreadLocalMap 的 set() 方法中,有一个阈值(默认为 ThreadLocalMap.Entry 数组长度的 1/4)。当 ThreadLocalMap 中的 Entry 对象被删除并且剩余的 Entry 数量大于这个阈值时,会触发一次清理操作。这种清理是启发式的,因为它不是每次操作都进行,而是基于一定的条件和概率。

说白了就是: 从当前节点开始,进行do-while循环检查清理过期key,结束条件是连续n次未发现过期key就跳出循环,n是经过位运算计算得出的,可以简单理解为数组长度的2的多少次幂次。

尽管有这些清理机制,但最佳实践仍然是在使用完 ThreadLocal 后显式调用 remove() 方法,以确保不再需要的值能够被及时回收。这样可以避免潜在的内存泄漏问题,并减少垃圾回收的压力。

五、总结

  • ThreadLocal 并不解决线程间共享数据的问题,它是通过增加资源来保证所有对象的线程安全

  • ThreadLocal 通过隐式的在不同线程内创建独立实例(数据)副本避免了实例线程安全的问题

  • 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题

  • ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题,因为Entry对键的弱引用,对值的强引用,如果不清理一些无用的对值的引用,可能引起内存泄漏的问题。

  • 对于内存泄漏的问题,ThreadLocal有以下几点措施:

    • ThreadLocal的remove()方法会移出无用的变量。
    • ThreadLocalMap 的 set 方法会通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身,从而防止内存泄漏
  • ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景。

补充:InheritableThreadLocal 支持子线程访问父线程的ThreadLocal

InheritableThreadLocal 是java.lang.ThreadLocal的一个子类,它的主要功能是允许子线程继承父线程中的ThreadLocal变量的值。 当一个新的线程被创建时,它可以访问到父线程中由InheritableThreadLocal管理的变量的副本。下面我们将深入探讨其实现原理。

实现原理概述

  1. 存储结构:

    • 每个Thread对象都有一个名为threadLocals的ThreadLocalMap,用于存储ThreadLocal变量及其值。
    • 同样地,每个Thread对象也有一个名为inheritableThreadLocals的ThreadLocalMap,专门用于存储InheritableThreadLocal变量及其值。
  2. 线程创建时的继承:

    • 当创建一个新线程时,父线程的InheritableThreadLocal变量会被复制到子线程的inheritableThreadLocals中。
    • 这个过程发生在Thread类的构造函数中,具体是在init方法内部调用的copyThreadLocals方法完成的。
  3. childValue方法:

    • InheritableThreadLocal有一个保护级别的方法childValue,这个方法允许子类重写以改变子线程继承的值。
    • 默认情况下,childValue方法返回父线程的值,但在子类中可以覆盖此方法以修改子线程获得的值。
  4. 线程执行时的访问:

    • 子线程通过get方法访问其inheritableThreadLocals中的InheritableThreadLocal变量。
    • 如果子线程的inheritableThreadLocals中没有找到相应的InheritableThreadLocal变量,则会调用initialValue方法来初始化该变量。
  5. 源码层面的细节:
    在Thread类的init方法中,有以下关键步骤:

public class Thread implements Runnable {

  	ThreadLocal.ThreadLocalMap threadLocals = null;
  	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

	void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc,
          boolean inheritThreadLocals, boolean createInDaemonThread) {
    ...
    if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }
    ...
}

这里 parent.inheritableThreadLocals 是父线程的InheritableThreadLocal映射,如果父线程中有InheritableThreadLocal变量,那么子线程就会调用ThreadLocal.createInheritedMap方法来复制这些变量。

createInheritedMap方法会在子线程的inheritableThreadLocals中创建一个ThreadLocalMap的副本,这样子线程就可以访问到父线程中InheritableThreadLocal的值了。

总结来说,InheritableThreadLocal的实现是通过在子线程创建时复制父线程的InheritableThreadLocal变量到子线程的inheritableThreadLocals中,从而实现了变量的继承性。

补充:引用类型总结

JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。

JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)

  • 强引用(StrongReference):以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

  • 软引用(SoftReference):如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。

  • 弱引用(WeakReference): 如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

  • 虚引用(PhantomReference):"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。

虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值