万万没想到,又被问ThreadLocal了

news2024/11/18 3:48:52

我待ThreadLocal如初恋,ThreadLocal虐我千百遍。但这一次,要彻底搞懂ThreadLocal

回顾一下面试名场面:

面试官:Handler如何做到与线程绑定的?

我:每个Handler只有一个相关联的Looper,线程绑定关键点正是Looper中和其内部的ThreadLocal类型的变量sThreadLocal。通过ThreadLocal完成了Looper和线程的绑定。

问:ThreadLocal原理是什么?

我内心:好像有个map,有个泛型,死活想不起啊。。。

10S后

我内心:我嘞个擦,ThreadLocal到底是什么鬼啊,为什么会有这么奇!怪!的!类!啊

打开源码开干吧

一.ThreadLocal源码注释及使用方法

看看第一段注释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

此类提供线程局部变量。这些变量与正常变量的不同之处在于,访问一个变量的每个线程(通过其 get 或 set 方法)都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,并将其状态与线程相关联(例如,用户 ID 或事务 ID)。

大概意思就是:ThreadLocal这个变量,顾名思义,就是用来管理(设置和获取)线程的局部变量的一个工具类。你要用这个ThreadLocal的时候,最好就是把这个定义成一个私有的静态变量。在不同的线程中,调用这个静态变量的set和get方法,都能设置或者获取属于自己的线程局部变量。当然这个变量的类型也是有限制的,就是ThreadLocal的泛型类型。

这里有个关键点:ThreadLocal通常定义成静态变量。

官方怕你不会用,注释第二段就给了一个例子

  import java.util.concurrent.atomic.AtomicInteger;
 
  public class ThreadId {
      // 原子整型,表示下一个要分配给下一个线程的id(这个线程ID是我们自己定义的,跟线程自身id不同)
      // Atomic integer containing the next thread ID to be assigned
      private static final AtomicInteger nextId = new AtomicInteger(0);

      // 重点:ThreadLocal变量,包含我们给每个线程定义的id(这里的ThreadLocal对象是个私有静态变量)
      // Thread local variable containing each thread's ID
      private static final ThreadLocal<Integer> threadId =
          new ThreadLocal<Integer>() {
              @Override protected Integer initialValue() {
                  // 初始化方法,当调用get方法第一次获取线程id的时候会初始化。
                  return nextId.getAndIncrement();
          }
      };

      // 返回当前线程的唯一id。如果没有的话就给他分配一个。
      // Returns the current thread's unique ID, assigning it if necessary
      public static int get() {
          return threadId.get();
      }
  }

上面例子里的ThreadId用起来是啥效果呢?我写个demo跑一下

val thread1 = Thread {
    Log.i("threadLocal", "thread1 :" + ThreadId.get())
    Log.i("threadLocal", "thread1 :" + ThreadId.get())
}
val thread2 = Thread {
    Log.i("threadLocal", "thread2 :" + ThreadId.get())
    Log.i("threadLocal", "thread2 :" + ThreadId.get())
}

thread1.start()
thread2.start()

//  打印结果如下
//  thread1 :0
//  thread2 :1
//  thread2 :1
//  thread1 :0

通过ThreadId这个工具类,可以很方便的去获取到自定义的线程id。

代码注释中会高频地出现一下两个名词:

ThreadLocal实例(ThreadLocal instances)

线程局部变量副本(copy of a thread-local variable)

这个局部变量副本很容易让人迷惑和误解。为了方便理解,我把ThreadLocal实例(也就是定义的那个静态变量)理解为“ThreadLocal工具对象”,把线程局部变量副本就叫做“线程局部变量”。线程中调用ThreadLocal工具对象可以方便地存取指定类型的变量。

第三段注释:

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只要线程处于活动状态并且 ThreadLocal 实例可访问,每个线程都对他的线程局部变量副本持有隐式引用。当线程销毁后,线程局部变量实例的所有副本都将可能被垃圾回收掉(除非存在对这些副本的其他引用)。

大概意思就是:

只要线程还活着,ThreadLocal对象还能访问,每个线程对他的线程局部变量就有引用。线程销毁之后,线程局部变量就会成为垃圾回收的对象。

二.关键方法get和set

ThreadLocal中有get和set方法,分别用来获取和设置当前线程的“线程局部变量”。

1.set

先看看set方法和其相关的方法,这三个方法都是定义在ThreadLocal中:

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取线程的成员变量threadLocals,类型为ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 如果map不为null,添加键值对 “ThreadLocal工具对象:value”
        map.set(this, value);
    } else {
        // 如果为null,给map初始化并添加键值对
        createMap(t, value);
    }
}

/**
* 获取线程的threadLocals成员变量,类型为ThreadLocal.ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

/**
* 为线程t创建ThreadLocalMap对象,构造函数参数为ThreadLocal工具对象,以及初始值
*/
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

说明一下,这里出现了一个新的类ThreadLocalMap,它是定义在ThreadLocal中的静态内部类,内部是一个哈希表,保存键值对,只不过保存键值对的key必须是ThreadLocal类型的,值就是上面说的“线程局部变量”。

ThreadLocalMap便是保存“ThreadLocal:Object”的哈希表。

那我又问了,Thread的ThreadLocalMap的成员变量是什么样的,就下面这样的,不仅有还有两个,我们用的是第一个。

public class Thread implements Runnable {

    // ... 省略

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

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

    // ... 省略
}

到这里要总结一下了,为了方便理解,用非常不专业的uml图,看看Thread、ThreadLocal、ThreadLocalMap他们三个的引用关系:

ThreadLocal以自身为key,从调用线程中获取value。

关键点来了:ThreadLocal乃真谋士,以身为饵(key),引天下入局。

2.get

继续看get方法

    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取线程的成员变量threadLocals,类型为ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 获取当前ThreadLocal工具类为key对应的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // map为null则返回初始值,默认初始值为null
        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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    protected T initialValue() {
        return null;
    }

set看懂的话,get就很容易理解,唯一要注意的是获取初始值initialValue这个方法可能在实际使用时可能被重写。

三.看看Looper怎么做的

Looper中用到ThreadLocal的地方全都在这里了:

public final class Looper {
    // 此处省略
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    // 此处省略

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    // 此处省略
}

sThreadLocal为静态变量。

prepare方法中为当前线程设置Looper。

三.总结

这个ThreadLocal写法确实剑走偏锋,常规思维容易记混乱,不过记住两个关键点就可以把整个逻辑回想起来:

1.ThreadLocal通常定义成静态变量

2.ThreadLocal以自身为key,从当前Thread的map中获取value

好了,至此分享结束。

此刻我的心情是:面试官快来问我ThreadLocal的问题吧,我已经迫不及待地想回答了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1438137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL进阶之路】SpringBoot 底层如何去和 MySQL 交互了呢?

SpringBoot 底层如何去和 MySQL 交互了呢&#xff1f; 我们在写做 Java 项目时&#xff0c;一般都是引入 MyBatis 框架来和 MySQL 数据库交互&#xff0c;如果需要在 MySQL 上执行什么语句&#xff0c;只需要在 Mapper.xml 文件中定义对应的 SQL 语句即可 那么他底层到底是如…

学习Android的第六天

目录 Android TextView 文本框 TextView 基础属性 范例 带阴影的TextView 范例 带边框的TextView 范例 带图片(drawableXxx)的TextView 范例1 范例2 使用autoLink属性识别链接类型 范例 TextView 显示简单的 HTML 范例1 范例2 SpannableString & Spannable…

Git合并多个commit

git rebase -i commitId 假设想要合并最后3个commit&#xff0c; git log显示 commit id 1 commit id 2 commit id 3 commit id 4 则执行git rebase -i commitId4. 注意是4&#xff0c;不是3. 然后&#xff0c;pick最老的commit (commit id 3). https://blog.csdn.net/qiao…

盲盒APP软件开发:开启全新购物体验

随着科技的飞速发展&#xff0c;盲盒APP软件成为了市场的新宠。盲盒APP软件开发不仅为消费者提供了全新的购物体验&#xff0c;同时也为商家带来了无限的商业机会。本文将深入探讨盲盒APP软件开发的各个方面&#xff0c;包括其概念、优势、开发流程以及未来发展趋势。 一、盲盒…

java Servlet 云平台教学系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc

一、源码特点 JSP 云平台教学系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助 系统采用serlvet dao bean&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/S模式开发。开发 环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据…

ref用法

目录 React中提供两种方法创建ref对象&#xff1a; 类组件获取 Ref 三种方式 ① Ref属性是一个字符串。 ② Ref 属性是一个函数。 ③ Ref属性是一个ref对象。 高级用法1&#xff1a;forwardRef 转发 Ref 高级用法2&#xff1a;ref实现组件通信 【ref作用】&#xff1a;最…

Vue3.4+element-plus2.5 + Vite 搭建教程整理

一、 Vue3Vite 项目搭建 说明&#xff1a; Vue3 最新版本已经基于Vite构建&#xff0c;关于Vite简介&#xff1a;Vite 下一代的前端工具链&#xff0c;前端开发与构建工具-CSDN博客 1.安装 并 创建Vue3 应用 npm create vuelatest 创建过程可以一路 NO 目前推荐使用 Vue R…

20240203在WIN10下使用GTX1080配置stable-diffusion-webui.git不支持float16精度出错的处理

20240203在WIN10下使用GTX1080配置stable-diffusion-webui.git不支持float16精度出错的处理 2024/2/3 21:23 缘起&#xff1a;最近学习stable-diffusion-webui.git&#xff0c;在Ubuntu20.04.6下配置SD成功。 不搞精简版本&#xff1a;Miniconda了。直接上Anacoda&#xff01; …

如何在 emacs 上开始使用 Tree-Sitter(windows)

文章目录 如何在emacs上开始使用Tree-Sitter&#xff08;windows&#xff09; 如何在emacs上开始使用Tree-Sitter&#xff08;windows&#xff09; 参考&#xff1a;“How to Get Started with Tree-Sitter”。 首先要有一个可运行的emacs&#xff0c;并且它支持Tree-Sitter&…

最新AI系统ChatGPT网站H5系统源码,支持Midjourney绘画局部编辑重绘,GPT语音对话+ChatFile文档对话总结+DALL-E3文生图

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

第3节、电机定速转动【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;本节介绍用定时器定时的方式&#xff0c;精准控制脉冲时间&#xff0c;从而控制步进电机速度。 一、计算过程 电机每一步的角速度等于走这一步所花费的时间&#xff0c;走一步角度等于步距角&#xff…

获取旁站 / C 段:第三方网站(附链接)

一、介绍 1.1 旁段 在网络安全的上下文中&#xff0c;"旁段"&#xff08;Pivot&#xff09;是指攻击者通过入侵一个网络中的一台计算机&#xff0c;然后利用该计算机作为跳板&#xff08;或者称之为“旁道”&#xff09;来访问其他计算机或网络资源的行为。 攻击者…

二进制安全虚拟机Protostar靶场(8)heap3 Fastbins unlink exploit

前言 这是一个系列文章&#xff0c;之前已经介绍过一些二进制安全的基础知识&#xff0c;这里就不过多重复提及&#xff0c;不熟悉的同学可以去看看我之前写的文章 heap3 程序静态分析 https://exploit.education/protostar/heap-three/#include <stdlib.h> #include …

二进制安全虚拟机Protostar靶场(7)heap2 UAF(use-after-free)漏洞

前言 这是一个系列文章&#xff0c;之前已经介绍过一些二进制安全的基础知识&#xff0c;这里就不过多重复提及&#xff0c;不熟悉的同学可以去看看我之前写的文章 heap2 程序静态分析 https://exploit.education/protostar/heap-two/#include <stdlib.h> #include &…

亚信安慧AntDB领航分布式数据库的突破之路

随着互联网技术的迅猛发展&#xff0c;大数据时代的到来&#xff0c;数据库的需求不断增长。在这样的背景下&#xff0c;国产分布式数据库正逐渐崭露头角&#xff0c;AntDB作为其中的重要代表&#xff0c;也积极参与到了这场竞争中。作为国内的技术创新者&#xff0c;AntDB不仅…

UE4 C++ 动态加载类和资源

动态加载类和资源&#xff1a;指在运行时加载 .cpp void AMyActor::BeginPlay() {Super::BeginPlay();//动态加载资源UStaticMesh* MyTempStaticMesh LoadObject<UStaticMesh>(nullptr,TEXT("/Script/Engine.StaticMesh/Game/StarterContent/Shapes/Shape_NarrowC…

2024过年期间可以开通股票账户吗?找哪家证券公司开户佣金手续费最低?

炒股前的准备工作包括以下几个方面&#xff1a; 学习基础知识&#xff1a;了解股市的基本知识&#xff0c;包括股票的基本概念、交易方式、股市规则等&#xff0c;可以通过阅读相关书籍、参加培训班或利用在线教育资源进行学习。 制定投资策略&#xff1a;根据个人的风险承受能…

Ubuntu22.04切换系统cuda版本

由于最近项目要求的cuda版本有差异&#xff0c;而在Ubuntu中可以通过切换cuda来满足需求&#xff0c;现记录如下。 1、按照 Ubuntu22.04与深度学习配置 中的cuda安装章节&#xff0c;将需要的cuda版本下载到本地并进行安装。 2、cuda安装完成后修改bashrc文件内容 sudo gedit …

c#cad 创建-正方形(四)

运行环境 vs2022 c# cad2016 调试成功 一、程序说明 创建一个正方形&#xff0c;并将其添加到当前活动文档的模型空间中。 程序首先获取当前活动文档和数据库&#xff0c;并创建一个编辑器对象。 然后&#xff0c;使用事务开始创建正方形的操作。获取模型空间的块表记录&a…

webrtc native api的几个要点

文章目录 基本流程状态回调类sdp的中媒体行pc对象 基本流程 webrtc native的接口&#xff0c;主要就是围绕着PeerConnection对象&#xff0c;一个PeerConnection对象它代表了一次音视频会话。 那么通过PeerConnection对象建立音视频通话&#xff0c;包括如下步骤&#xff1a; …