Java并发编程——Threadlocal源码解析

news2024/12/23 1:08:25

Threadlocal源码解析

    • 一、基本结构
    • 二、ThreadLocal操作
      • set操作
      • get操作
      • remove操作
    • 三、内存泄露?
    • 四、ThreadLocalMap
      • 核心变量
      • 数组下标计算方式
      • 阈值计算
      • 扩容
      • 下标冲突(hash冲突)

从名称上来看可以理解为线程本地变量,也可以认为是线程局部变量,线程与线程之间都是隔离的,所以说也是线程安全的,是典型的空间换时间的设计理念

一、基本结构

先看一下该类的重要成员和重要的内部类:

/** 以下三个不用管,只需要记住是用来计算类的Hash的就可以了 */
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
// 用来保证hash更均匀
private static final int HASH_INCREMENT = 0x61c88647;

// 重点内部类(内容省略,只需要记住是个Map)
static class ThreadLocalMap {}


上面最关键的就是ThreadLocalMap 这个Map,其他三个都是用来计算Threadlocal自身的hash的,因为在ThreadLocalMap 里面Key 就是Threadlocal自身,所以综合下来线程ThreadThreadLocalMap 关系如下:
在这里插入图片描述

每个线程都会持有一个ThreadLocalMap, Threadlocal只是里面的key而已,一个线程要是有多个变量那应该是这样:

在这里插入图片描述

ThreadLocalMap和线程Thread是如何关联的呢?注意我图示Thread下面的 threadLocals了嘛,线程类内部本身就有threadLocals这个属性,这个属性就是ThreadLocalMap,如下:

在这里插入图片描述

二、ThreadLocal操作

经过上面相信大家已经对ThreadThreadLocalMapThreadLocal三者结构有一定了解了,那ThreadLocal到底干了些什么?其实就是把自身当做key,作为一个中间者来交互,实现增删改操作

set操作

逻辑很简单:

  • 从线程中获取map,获取到了就把自身作为key加上值 写入到map中
  • map不存在就重新创建一个给线程,并写入key-value

源代码如下:

public void set(T value) {
    Thread t = Thread.currentThread();
    // 获取当前线程里面的ThreadLocalMap 
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // map不为空 就把自身当做key,从map里面取值
        map.set(this, value);
    else
        // map为空则为线程创建一个map 并写入值
        createMap(t, value);
}

// 为线程创建一个map
void createMap(Thread t, T firstValue) {
    // 创建一个map赋值给线程的threadLocals属性
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 线程获取Map就是获取线程的threadLocals属性
ThreadLocalMap getMap(Thread t) {
  return t.threadLocals;
}

get操作

也是一样从线程中获取map,然后从以自身为key从map中获取value

如果map不存在则会新建一个map,set 一个null值,并返回null值

源代码如下:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // map存在 则以自身为key 从map中获取value
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // map不存在 则创建一个新的map 并返回null
    return setInitialValue();
}
// 初始化map
private T setInitialValue() {
    // 这个值就是null 下面返回的也是null  set的值也是null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

remove操作

移除也简单就是以自身为key,移除值

源代码如下:

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

三、内存泄露?

上面三个操作基本上就是ThreadLocal全部了(下面说map的),ThreadLocal有关最多的话题就是内存问题,难道用ThreadLocal很容易发生内存溢出吗?

个人觉得ThreadLocal跟内存溢出没关系,从根本上看ThreadLocalMap 是作为属性与Thread绑定的,Thread不结束,map的引用就不会释放,所以内存不会被回收,本质上是线程没结束,内存释放不了;从使用角度上来讲,我线程没结束,我也意识到了要清除map里面部分值来释放内存,但是突然发现清不了,因为key没了,value还存在,为什么会这样?

因为ThreadLocal作为key是弱引用,可以被回收,而value是强引用,试想你key都没了,还怎么移除value?

ThreadLocalMap内Entry定义如下,继成了WeakReference :

在这里插入图片描述

所以说如果Thread能很快的正常结束,那无事发生,如果Thread的生命周期很长,长时间存在的那种,那么在使用ThreadLocal的时候就要注意,不用的变量要及时的remove掉;线程不都很快结束吗?什么线程会长时间存在?想想线程池的核心线程

四、ThreadLocalMap

关于这个map我就不分操作一个一个说了,说几个关键的

核心变量

// 默认大小
private static final int INITIAL_CAPACITY = 16;

// 数组       
private Entry[] table;

// 元素数量
private int size = 0;

// 扩容因子
private int threshold; 

底层数据结构就是数组,但没有链表了,因为不是以链表的形式来解决hash冲突的

数组下标计算方式

threadLocalHashCode & (length-1)

threadLocalHashCode:就是开头说的ThreadLocal那三个来计算得出的

length:数组长度

阈值计算

数组长度的2/3

源代码如下:

private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

扩容

  • 双倍扩容
  • 遍历之前的key-value,重新计算key的下标,然后放入新数组

源代码如下:

private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    // 扩容两倍
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;
    
    // 遍历之前的数组 重新计算之前key的hash 在写入新数组
    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                // 如果key为null  则把value也置为null 
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    // 如果下标已经有值了(hash冲突了) 则往后找空位
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }
    // 重新计算阈值
    setThreshold(newLen);
    size = count;
    table = newTab;
}

下标冲突(hash冲突)

与HashMap不同,这个是用开放寻址法(线性探测法),就是说如果当前下标冲突了,就往后找空位,直至找到一个空位放入

源代码如下:

// 传入长度和下标索引
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

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

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

相关文章

(JAVA)认识Java中的数据类型和变量

文章目录前言1.字面常量2. 数据类型3.变量3.1 变量概念3.2 语法格式3.3 整形变量3.4 浮点型变量3.5 字符型变量3.6布尔类型变量3.7 类型转换3.7.1 隐式转换&#xff08;自动类型转换&#xff09;3.7.2 显示转换 &#xff08;强制类型转换&#xff09;3.8 类型提升4. 字符串类型…

驱动开发:内核层InlineHook挂钩函数

在上一章《驱动开发&#xff1a;内核LDE64引擎计算汇编长度》中&#xff0c;LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度&#xff0c;本章将在此基础之上实现内联函数挂钩&#xff0c;内核中的InlineHook函数挂钩其实与应用层一致&#xff0c;都是使用劫持执行流并跳…

三类基于贪心思想的区间覆盖问题【配套资源详解】

博主主页&#xff1a;Yu仙笙 配套资源&#xff1a;三类基于贪心算法覆盖问题-C文档类资源-CSDN下载 目录 三类基于贪心思想的区间覆盖问题 情形1&#xff1a;区间完全覆盖问题 描述&#xff1a; 样例&#xff1a; 解题过程: 例题&#xff1a; 题意&#xff1a; 例题&#xff1a…

深入理解Kafka服务端之索引文件及mmap内存映射

深入理解Kafka服务端之索引文件及mmap内存映射 - 墨天轮 一、场景分析 Kafka在滚动生成新日志段的时候&#xff0c;除了生成日志文件(.log)&#xff0c;会同时生成一个偏移量索引文件(.index)、一个时间戳索引文件(.timeindex)和一个已中止事务索引文件(.txnindex)。 由于索引写…

JVM面试高频问题

一、进程与线程 在谈JVM的这些问题前&#xff0c;我们先来复习一下有关线程和进程的关系 进程可以看作是程序的执行过程。一个程序的运行需要CPU时间、内存空间、文件以及I/O等资源。操作系统就是以进程为单位来分配这些资源的&#xff0c;所以说进程是分配资源的基本单位。线…

C语言函数章--该如何学习函数?阿斗看了都说会学习了

前言 &#x1f47b;作者&#xff1a;龟龟不断向前 &#x1f47b;简介&#xff1a;宁愿做一只不停跑的慢乌龟&#xff0c;也不想当一只三分钟热度的兔子。 &#x1f47b;专栏&#xff1a;C初阶知识点 &#x1f47b;工具分享&#xff1a; 刷题&#xff1a; 牛客网 leetcode笔记软…

【Python入门指北】 发邮件与正则表达式

文章目录邮件发送一、群发邮件二、指定用户发邮件正则表达式一、预备知识正则1. 正则介绍2. 陷阱3. 特殊的字符二、 re 模块的方法1 常用方法2. 正则分组总结邮件发送 #第三方模块 yagmail #pip3 install yagmailimport yagmail""" 项目需求 yag yagmail.SMTP(u…

MyBatis Plus实现动态字段排序

利用周末时间&#xff0c;对已有的项目进行了升级&#xff0c;原来使用的是tkmybatis&#xff0c;改为mybatis plus。但是由于修改了返回数据的格式&#xff0c;前端页面字段排序失效了&#xff0c;需要刷新表格才会排序。页面效果如下 easyui的数据表格datagrid支持多字段排序…

【仿牛客网笔记】Spring Boot实践,开发社区登录模块-账号设置,检查登录

首先访问账号设置的页面。 新建一个Controller,用过RequestMapping生成访问路径 上传头像 首先打开配置文件&#xff0c;配置一下将文件配置到哪里。 直接在Controller存了&#xff0c; 更新的时候掉Map&#xff0c;参数为id和路径。 注入日志对象后&#xff0c;通过Val…

SpringBoot项目启动执行任务的几种方式

经过整理后得到以下几种常用方式&#xff0c;供大家参考。 1. 使用过滤器 init() &#xff1a;该方法在tomcat容器启动初始化过滤器时被调用&#xff0c;它在 Filter 的整个生命周期只会被调用一次。可以在这个方法中补充想要执行的内容。 Component public class MyFilter …

CTF竞赛网络安全大赛(网鼎杯 )Web|sql注入java反序列化

CTF竞赛网络安全大赛题目考点 sql注入 java反序列化 网鼎杯解题思路 题目一打开是这样的界面 下载题目的附件,并用jd-gui.exe打开 核心代码如下 Test代码 `` package 部分class;import cn.abc.common.bean.ResponseCode; import cn.abc.common.bean.ResponseResult; impor…

持续交付中流水线构建完成后就大功告成了吗?别忘了质量保障

上期文章我结合自己的实践经验&#xff0c;介绍了持续交付中流水线模式的软件构建&#xff0c;以及在构建过程中的3个关键问题。我们可以看出&#xff0c;流水线的软件构建过程相对精简、独立&#xff0c;只做编译 和打包两个动作。 但需要明确的是&#xff0c;在持续交付过程…

网课查题接口使用方法

网课查题接口使用方法 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点…

Hadoop面试题汇总-20221031

Hadoop面试题汇总 HDFS部分 1、请描述HDFS的写流程。 答&#xff1a; 首先由客户端向 NameNode 发起文件上传请求&#xff0c;NameNode 检查文件要上传的目录&#xff0c;并鉴权。如果上传用户对此目录有权限&#xff0c;则允许客户端进行上传操作。客户端接收到允许指令后&…

本科毕业论文内容必须有国内外文献综述吗?

不知不觉间整个暑假变过去了&#xff0c;现在大部分的大学生都已经开学了。2023届毕业的学生现在也开始借鉴毕业论文的选题工作。但是无论是现在正在选题的大四的同学们还是还在上大一大&#xff0c;二大三的同学们都对毕业论文这4个字有着天生的恐惧感。因为对于大多数人来说&…

阿里为何禁止在对象中使用基本数据类型

大家好&#xff0c;我是一航&#xff01; 前两天&#xff0c;因为一个接口的参数问题&#xff0c;和一位前端工程师产生了一些分歧&#xff0c;需求很简单&#xff1a; 根据一个数值类型&#xff08;type 取值范围1&#xff0c;2&#xff0c;3&#xff09;来查询数据&#xff…

HTML+CSS+JavaScript七夕情人节表白网页【樱花雨3D相册】超好看

这是程序员表白系列中的100款网站表白之一&#xff0c;旨在让任何人都能使用并创建自己的表白网站给心爱的人看。 此波共有100个表白网站&#xff0c;可以任意修改和使用&#xff0c;很多人会希望向心爱的男孩女孩告白&#xff0c;生性腼腆的人即使那个TA站在眼前都不敢向前表白…

pandas 基本数据

目录 1. pandas 简介 2. pandas 基本数据结构 2.1 Series 类型 2.1.1 索引-数据的行标签 2.1.2 值 2.1.3 切片 2.1.4 索引赋值 2.2 DataFrame 类型 1. pandas 简介 一般导入的形式&#xff1a;import pandas as pd 2. pandas 基本数据结构 python 的数据结构&#xff1a…

python爬虫之Scrapy框架,基本介绍使用以及用框架下载图片案例

一、Scrapy框架简介 Scrapy是:由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据&#xff0c;只需要实现少量的代码&#xff0c;就能够快速的抓取。 Scrapy使用了Twisted异步网络框架来处理网络通信&#xf…

Servlet篇 —— 我的第一个Servlet程序

☕导航小助手☕ &#x1f35a;写在前面 &#x1f35c;一、Maven的介绍 &#x1f371;​二、第一个Servlet的创建 &#x1f354;&#x1f354;2.1 创建项目 &#x1f969;&#x1f969;​2.2 引入依赖 &#x1f9aa;&#x1f9aa;​2.3 创建目录 &#x1f363;&#x1f363;2.4…