《面试1v1》ThreadLocal

news2024/11/18 8:20:08

我是 javapub,一名 Markdown 程序员从👨‍💻,八股文种子选手。

面试官: 你好,请问你对 ThreadLocal 有了解吗?

候选人: 您好,我知道 ThreadLocal 是一个 Java 中的类,它可以让每个线程都拥有自己的变量副本,从而避免了线程安全问题。

面试官: 非常好,那你能否详细介绍一下 ThreadLocal 的使用方法?

候选人: 当然可以。ThreadLocal 的使用方法非常简单,我们只需要创建一个 ThreadLocal 对象,然后调用它的 set 方法来设置当前线程的变量值,调用 get 方法来获取当前线程的变量值即可。下面是一个简单的示例代码:

public class ThreadLocalDemo {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            threadLocal.set("Hello from thread1");
            System.out.println(threadLocal.get());
        });

        Thread thread2 = new Thread(() -> {
            threadLocal.set("Hello from thread2");
            System.out.println(threadLocal.get());
        });

        thread1.start();
        thread2.start();
    }
}

这个示例代码中,我们创建了一个 ThreadLocal 对象,并在两个线程中分别设置了不同的变量值。由于每个线程都有自己的变量副本,所以这两个线程互不干扰,输出的结果也是不同的。

面试官: 非常好,那你能否解释一下 ThreadLocal 的原理是什么?

候选人: 当然可以。ThreadLocal 的原理其实很简单,它是通过一个 ThreadLocalMap 对象来存储每个线程的变量副本的。当我们调用 ThreadLocal 的 set 方法时,实际上是在当前线程的 ThreadLocalMap 对象中存储了一个键值对,其中键是当前 ThreadLocal 对象,值是我们设置的变量值。当我们调用 ThreadLocal 的 get 方法时,实际上是在当前线程的 ThreadLocalMap 对象中查找当前 ThreadLocal 对象对应的变量值。

下面是 ThreadLocalMap 的源码实现,我在代码中加了注释,希望能够帮助您更好地理解:

class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    // 初始容量为 16
    private static final int INITIAL_CAPACITY = 16;

    // 扩容因子为 2
    private static final float LOAD_FACTOR = 0.75f;

    // 存储键值对的数组
    private Entry[] table;

    // 数组中键值对的数量
    private int size = 0;

    // 下一个要清理的键值对的索引
    private int threshold;

    // 清理键值对的阈值
    private void setThreshold(int len) {
        threshold = (int) (len * LOAD_FACTOR);
    }

    // 获取键值对的值
    private Object getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key) {
            return e.value;
        } else {
            return null;
        }
    }

    // 设置键值对的值
    private void setEntry(ThreadLocal<?> key, Object value) {
        // 清理键值对
        expungeStaleEntries();

        // 计算键值对的索引
        int i = key.threadLocalHashCode & (table.length - 1);

        // 如果该位置已经有键值对了,则往后查找空位置
        for (Entry e = table[i]; e != null; e = table[i = nextIndex(i, table.length)]) {
            ThreadLocal<?> k = e.get();

            // 如果找到了相同的 ThreadLocal 对象,则直接替换值
            if (k == key) {
                e.value = value;
                return;
            }

            // 如果找到了一个空的位置,则插入新的键值对
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }

        // 如果该位置没有键值对,则插入新的键值对
        table[i] = new Entry(key, value);
        int sz = ++size;
        if (sz >= threshold) {
            // 扩容
            rehash();
        }
    }

    // 清理过期的键值对
    private void expungeStaleEntries() {
        Entry[] tab = table;
        int len = tab.length;
        for (int i = 0; i < len; i++) {
            Entry e = tab[i];
            if (e != null && e.get() == null) {
                // 清理过期的键值对
                expungeStaleEntry(i);
            }
        }
    }

    // 清理指定位置的键值对
    private void 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;
                }
            }
        }
    }

    // 扩容
    private void rehash() {
        expungeStaleEntries();

        // 如果当前数组长度已经达到最大值,则不再扩容
        if (size >= threshold - threshold / 4) {
            return;
        }

        int newCapacity = table.length * 2;
        Entry[] newTable = new Entry[newCapacity];
        int count = 0;

        for (Entry e : table) {
            if (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                } else {
                    int i = k.threadLocalHashCode & (newCapacity - 1);
                    while (newTable[i] != null) {
                        i = nextIndex(i, newCapacity);
                    }
                    newTable[i] = e;
                    count++;
                }
            }
        }

        setThreshold(newCapacity);
        size = count;
        table = newTable;
    }

    // 计算下一个索引
    private static int nextIndex(int i, int len) {
        return (i + 1) % len;
    }
}

面试官: 非常好,那你能否解释一下 ThreadLocal 的优缺点是什么?

候选人: 当然可以。ThreadLocal 的优点是它可以让每个线程都拥有自己的变量副本,从而避免了线程安全问题。另外,ThreadLocal 的使用方法非常简单,只需要调用 set 和 get 方法即可。

ThreadLocal 的缺点是它可能会导致内存泄漏问题。由于每个线程都有自己的变量副本,如果我们没有及时清理这些变量副本,就可能会导致内存泄漏。另外,ThreadLocal 的使用也可能会导致上下文切换的开销增加,因为每个线程都需要维护自己的变量副本。

面试官: 非常好,你对 ThreadLocal 的了解非常深入,今天就到这里吧。

候选人: 谢谢您的提问,我很高兴能够分享我的知识。

最近我在更新《面试1v1》系列文章,主要以场景化的方式,讲解我们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣可以关注JavaPub追更!

🎁目录合集:

Gitee:https://gitee.com/rodert/JavaPub

GitHub:https://github.com/Rodert/JavaPub

http://javapub.net.cn

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

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

相关文章

【坐标变换】坐标系坐标变换简单推导--未完待续

如图所示&#xff0c;假设已知坐标系 ( X , Y ) (X,Y) (X,Y)&#xff0c;旋转后的坐标系为 ( X ′ , Y ′ ) (X,Y) (X′,Y′)&#xff0c;旋转角度为 θ \theta θ&#xff0c;假设点p在 ( X , Y ) (X,Y) (X,Y)坐标系下为 ( x , y ) (x,y) (x,y)&#xff0c;坐标在旋转后的坐标…

速来!谷歌师兄的LeetCode刷题笔记开源了!

有小伙伴私聊我说刚开始刷LeetCode的时候&#xff0c;感到很吃力&#xff0c;刷题效率很低。我以前刷题的时候也遇到这个问题&#xff0c;直到后来看到这个谷歌师兄总结的刷题笔记&#xff0c;发现LeetCode刷题都是套路呀&#xff0c;掌握这些套路之后&#xff0c;就变得非常简…

kubernetes高可用+harbor高可用

kubernetes高可用harbor高可用 基于kubeadm安装kubernetes高可用集群全部主机环境初始化双主节点部署keepalive双主节点初始化kubeadm在k8smaster1节点上初始化k8s在k8smaster2节点上做扩容操作 harbor高可用集群初始化harbor1节点安装环境在另一台节点上配置使用私有harbor仓库…

初学QT:使用QtDesigner绘制一个简单的界面(Day01)

关于Qt 打算在这里记录我学习qt过程中遇见的问题的收获 今天是学习qt的第一天&#xff0c;首先找了一个界面打算照着这个界面写一个一样的 因为是第一天&#xff0c;所以我用的是qt designer写的 其中遇到的问题&#xff1a; 设置背景图片 首先不能直接添加图片到背景图片中…

如何在华为OD机试中获得满分?Java实现【分界线】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

在 Alma Linux 9 上安装 Node.js 的 3 种不同方法

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时&#xff0c;用于构建快速、可扩展的网络应用程序。在 Alma Linux 9 上安装 Node.js 可以为开发者提供强大的工具和库来开发服务器端应用程序。 本文将介绍三种不同的方法来安装 Node.js 在 Alma Linux 9 上。 1. 方法一…

LLMs的自动化工具系统(HuggingGPT、AutoGPT、WebGPT、WebCPM)

在前面两篇博文中已经粗略介绍了增强语言模型和Tool Learning&#xff0c;本篇文章看四篇代表性的自动化框架&#xff0c;HuggingGPT、AutoGPT、WebGPT、WebCPM。 Augmented Language Models&#xff08;增强语言模型&#xff09;Toolformer and Tool Learning&#xff08;LLM…

chatgpt赋能python:了解PythonSpace:Python编程中的重要概念

了解Python Space&#xff1a;Python编程中的重要概念 Python Space是Python编程的一个关键概念&#xff0c;可以帮助你更好地理解Python中的命名空间和作用域。在这篇文章中&#xff0c;我们将深入探讨Python Space&#xff0c;介绍命名空间的概念&#xff0c;讨论命名空间和…

支付系统设计四:支付核心设计03-快捷发送短信(失败转代扣)

文章目录 前言一、背景1. 应用架构2. 分层支撑机制 二、银行卡快捷支付1. 用户操作流程2. 系统执行流程--正常2.1 发送短信2.2 短信确认 3. 系统执行流程--异常3.1 异常环节3.1.1 路由失败3.1.2 调用支付渠道失败 3.2 异常处理3.2.1 路由失败3.2.2 调用支付渠道失败 4. 流程解析…

指导实验心得5篇实用技巧

指导实验心得1 我觉得化工原理实验是一门验证性课程&#xff0c;它把我们在化工原理学到的各种单元操作化为实实在在的东西&#xff0c;而让我们把学到的知识认识到它的实在性。流体输送——离心泵、过滤——板框压滤机、对流传热——套管式换热器、吸收蒸馏——填料塔板式塔、…

AF594 NHS,Alexa Fluor594 NHS Ester,AF 594 NHS 活化酯,用于成像和流式细胞术中的稳定信号生成

【产品描述】 陕西新研博美生物科技有限公司供应的​Alexa Fluor594是一种鲜红色染料。Alexa Fluor用于成像和流式细胞术中的稳定信号生成 594染料是水溶性的&#xff0c;并且从pH 4到pH 10对pH不敏感。Alexa Fluor 594染料与多种抗体、肽、蛋白质、示踪剂和扩增底物偶联&#…

java内存问题

各种OOM的情况 1. 堆溢出-java.lang.OutOfMemoryError: Java heap space。 2. 栈溢出-java.lang.OutOfMemorryError。 3. 栈溢出-java.lang.StackOverFlowError。 4. 元信息溢出-java.lang.OutOfMemoryError: Metaspace。 5. 直接内存溢出-java.lang.OutOfMemoryError: Direct …

软件开发工程师个人简历模板3篇

软件开发工程师个人简历模板篇1 姓 名&#xff1a; 张先生 性 别&#xff1a; 男 婚姻状况&#xff1a; 未婚 民 族&#xff1a; 汉族 户 籍&#xff1a; 广东-珠海 年 龄&#xff1a; 28 现所在地&#xff1a; 广东-珠海 身 高&#xff1a; 168cm 希望地区&#xff1a; …

Toolformer and Tool Learning(LLMs如何使用工具)

大模型的能力让学术和工业界都对通用人工智能的未来充满幻想&#xff0c;在前一篇博文中已经粗略介绍&#xff0c; Augmented Language Models&#xff08;增强语言模型&#xff09; ALM的两大思路是推理和工具&#xff0c;本篇博文整理两篇关于Toolformer或Tool Learning的论…

web实现日历、阳历农历之间相互转换、npm、push、unshift、includes、innerHTML

文章目录 1、原生web实现效果图htmlJavaScriptstyle vue2实现htmlJavaScript 1、原生web实现 效果图 html <div class"box"><div class"week"><div>星期日</div><div>星期一</div><div>星期二</div><…

Three.js--》建模软件如何加载外部3D模型?

目录 三维建模软件的介绍 Blender官方文档介绍 Blender软件安装 GLTF格式简介 gltf不同文件形式 看过我之前讲解的three文章的人都知道&#xff0c;我在创建模型的时候都没有使用three.js自带的一些简单模型&#xff0c;而是引入外部的模型并加载到页面上&#xff0c;简言…

数据库基础——1.数据库概述

从这篇文章我们开始学习数据库的相关知识 目录 1.为什么要使用数据库 2.数据库与数据库管理系统 2.1相关概念 2.2数据库与数据库管理系统的关系 ​编辑2.3常见的数据库管理系统 2.4常见的数据库介绍 3.MySQL介绍 3.1概述 3.2关于MySQL8.0 3.3 Oracle vs MySQL 4.RD…

Java 反序列化漏洞

反序列化漏洞是指程序在反序列化期间&#xff0c;通过特殊的调用链引发的一系列安全问题。编程语言中只要存在序列化&#xff0c;反序列化功能就可能存在反序列化的安全问题。这里只针对Java和PHP进行讨论。 序列化漏洞概述 序列化的存在主要是为了存储和传输&#xff0c;将这…

Redis入门篇-初

结束时长 Redis十大数据类型 基本目录 实际的类型是没有被红框框选的10个类型 Strings 1 Lists 2 Sets 3 Hashes 4 Sorted sets 5 Streams 6 Geospatial 7 HyperLogLog 8 Bitmaps 9 Bitfields 10类型展示 Strings --> HelloRedis Lists [A>B>C>C] Sets {A<…

第七章 文件读写

内容框图 7.1 文件读写介绍 文件打开和关闭 用word编写一份简历&#xff0c;应该有哪些流程&#xff1f; 打开word软件&#xff0c;新建一个word文件写入个人简历信息保存文件关闭word软件 同样&#xff0c;编程中操作文件的整体过程类似。 打开文件&#xff0c;或者新建立一个…