《ThreadLocal使用与学习总结:2023-12-15》由浅入深全面解析ThreadLocal

由浅入深全面解析ThreadLocal

目录

  • 由浅入深全面解析ThreadLocal
    • 简介
    • 基本使用
    • ThreadLocal与synchronized的区别
    • ThreadLocal现在的设计(JDK1.8)
    • ThreadLocal核心方法源码分析
    • ThreadLocalMap源码分析
    • 弱引用与内存泄露(内存泄漏和弱引用没有直接关系)
    • ThreadLocal核心源码(Hash冲突解决)

简介

  1. 线程并发:在多线程并发的场景下使用
  2. 传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
  3. 线程隔离:每个线程的变量都是独立的,不会相互影响

基本使用

  1. 常用方法
    在这里插入图片描述

  2. 代码案例实现
    (1) 不使用ThreadLocal时模拟多线程存取数据

public class ThreadLocalDemo1 {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLocalDemo1 threadLocalDemo = new ThreadLocalDemo1();

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    /**
                     * 每一个线程存一个变量,过一会取出这个变量
                     */
                    threadLocalDemo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("------------------------");
                    System.out.println(Thread.currentThread().getName() + "----->" + threadLocalDemo.getContent());
                }
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

结果:

------------------------
线程0----->线程4的数据
------------------------
线程4----->线程4的数据
------------------------
线程2----->线程4的数据
------------------------
线程3----->线程4的数据
------------------------
线程1----->线程4的数据

(2) 使用ThreadLocal对多线程进行数据隔离,把数据绑定到ThreadLocal
(传统解决方案首先想到的就是加锁,确实可以实现,但是却牺牲了效率,需要等待上一个线程之行结束才可以往下之行)

public class ThreadLocalDemo2 {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    private String content;

    public String getContent() {
        return threadLocal.get();
    }

    public void setContent(String content) {
        threadLocal.set(content);
    }

    public static void main(String[] args) {
        ThreadLocalDemo2 threadLocalDemo2 = new ThreadLocalDemo2();

        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    /**
                     * 每一个线程存一个变量,过一会取出这个变量
                     */
                    threadLocalDemo2.setContent(Thread.currentThread().getName()+"的数据");
                    System.out.println("------------------------");
                    System.out.println(Thread.currentThread().getName() + "----->" + threadLocalDemo2.getContent());
                }
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

结果:

------------------------
------------------------
------------------------
线程3----->线程3的数据
------------------------
线程2----->线程2的数据
线程1----->线程1的数据
线程0----->线程0的数据
------------------------
线程4----->线程4的数据

ThreadLocal与synchronized的区别

二者都是用来处理多线程并发访问的问题,但是二者的原理和侧重点不一样,简要说就是,ThreadLocal牺牲了空间,而synchronized是牺牲了时间来保证线程安全(隔离)。
在这里插入图片描述
总结:在上述的案例当中,使用ThreadLocal更为合理,这样保证了程序拥有了更高的并发性。

ThreadLocal现在的设计(JDK1.8)

  1. 简介
    每一个Thread维护一个ThreadLocalMap,这个Map的key为ThreadLocal实例本身,而value则为实际存储的值。
  2. 具体过程
    (1)每一个Thread内部都有一个Map(ThreadLocalMap)
    (2)Map里面存储的ThreadLocal对象(key)和线程的变量副本(value)
    (3)Thread的Map是由ThreadLocal来维护的,由ThreadLocal负责向Map获取和设置线程的变量值。
    (4)对于线程获取值,每一个副本只能获取当前线程本地的副本值,别的线程无法访问到,互不干扰,实现了线程隔离。
  3. 对比与1.8之前的设计(相当于Thread与ThreadLocal的角色互换了)
    在这里插入图片描述
  4. 1.8设计的好处
    (1)每个Map存储的Entry数量变少了(因为实际状况下Thread比ThreadLocal多)
    (2)当Thread销毁时,ThreadLocalMap也会随之销毁,避免内存的浪费

ThreadLocal核心方法源码分析

在这里插入图片描述

  1. get方法源码
public T get() {
		// 获取当前线程
        Thread t = Thread.currentThread();
        // 获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // map不为空时,获取里面的Entry
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                // 返回结果
                return result;
            }
        }
        // 没有则赋值初始值null并返回
        return setInitialValue();
    }
  1. set方法源码
public void set(T value) {
		// 获取当前线程
        Thread t = Thread.currentThread();
        // 获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// 不为空直接set
            map.set(this, value);
        } else {
        	// map为空则创建并set
            createMap(t, value);
        }
    }
  1. initialValue方法返回初始值(protected修饰为了让子类覆盖设计的)需要自定义初始值可以重写该方法
protected T initialValue() {
        return null;
    }
  1. remove方法
public void remove() {
		 // 获取当前线程的ThreadLocalMap
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
         	// 移除ThreadLocalMap
             m.remove(this);
         }
     }
  1. setInitialValue方法
private T setInitialValue() {
		// 得到初始化值null
        T value = initialValue();
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取线程中ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        // map存在的话把null设置进去,不存在则创建一个并将null设置进去
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        // 如果当前ThreadLocal属于TerminatingThreadLocal(关闭的ThreadLocal)则register(注册)到TerminatingThreadLocal
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

ThreadLocalMap源码分析

  1. 简介
    ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,是独自设计实现Map功能,内部的Entry也是独立的。
  2. 结构图解
    在这里插入图片描述
  3. 成员变量
		// Entry类,继承弱应用,为了和Thread的生命周期解绑
		static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

        /**
         * The initial capacity -- MUST be a power of two.
         * 初始容量,必须是二的幂
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         * 根据需要调整大小。长度必须是2的幂。
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         * table中的entrie数量
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         * 要调整大小的下一个大小值
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         * 设置调整大小阈值以维持最坏的2/3负载因子
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         * 增量一
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         * 减量一
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

弱引用与内存泄露(内存泄漏和弱引用没有直接关系)

  1. 内存泄漏/溢出概念
    (1)Memory overflow:内存溢出,没有足够的空间提供给申请者使用
    (2)Memory leak:内存泄漏,系统中已动态分配的堆内存由于某种原因无法释放或者没有释放,导致系统内存堆积,影响系统运行,甚至导致系统崩溃。内存泄漏终将导致内存溢出。
  2. 强/弱引用概念
    (1)Strong Referce:强引用,我们常见的对象引用,只要有一个强引用指向对象,也就表明还“活着”,这种状况下垃圾回收机制(GC)是不会回收的。
    (2)Weak Referce:弱引用,继承了WeakReferce的对象,垃圾回收器发现了只具有弱引用的对象,不管当前系统的内存是否充足,都会回收他的内存。
  3. 如果key,即Entry使用强引用,也无法避免内存泄漏
    因为Entry是在Thread当前线程中,生命周期和Thread一样,没有手动删除Entry时Entry就会内存泄漏。
  4. 也就是说,只要在调用完ThreadLocal后及时使用remove方法,才能避免内存泄漏
    在这里插入图片描述
    在这里插入图片描述

ThreadLocal核心源码(Hash冲突解决)

  1. 从构造方法入手

		ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
			// 初始化table
            table = new Entry[INITIAL_CAPACITY];
            // 计算索引在数组中的位置(核心代码)
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // 设置值
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            // 设置阈值(INITIAL_CAPACITY的三分之二)
            setThreshold(INITIAL_CAPACITY);
        }
  1. 重点分析int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
private final int threadLocalHashCode = nextHashCode();

private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    
private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

(1)这里定义了一个AtomicInteger,每次获取并加上HASH_INCREMENT(0x61c88647,这个值与斐波那契数(黄金分割)有关),是为了让哈希码能够均匀的分布在2的n次方的数组(Entry[])里面,也就尽可能避免了哈希冲突。
(2)hashcode & (INITIAL_CAPACITY - 1) 相当于hashcode % (INITIAL_CAPACITY - 1) 的高效写法,所以size必须为2的次幂,这样最大程度避免了哈希冲突。

  1. set方法源码分析
private void set(ThreadLocal<?> key, Object value) {
    		
            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)]) {
                 // 获取到该Entry对应的ThreadLocal
                ThreadLocal<?> k = e.get();
                if (k == key) {
                // key存在则覆盖value
                    e.value = value;
                    return;
                }
                // key为null但是值不为null,这说明了之前使用过,但是ThreadLocal被垃圾回收了,当前的Entry是一个陈旧的(Stale)元素
                if (k == null) {
                // key(ThreadLocal)不存在,则新Entry替换旧的Entry,此方法做了不少垃圾清理的动作,避免了内存泄漏。
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            // ThreadLocal中未找到key也没有陈旧的元素,此时则在这个位置新创建一个Entry
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // cleanSomeSlots用于清理e.get()为null的key,如果大于阈值(2/3容量)则rehash(执行一次全表扫描清理工作)
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
		/**
		* 线性探测法查找元素,到最后一个时重定位到第一个
		*/
		private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

(详细视频可前往B站黑马程序员)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python排序算法 直接插入排序法和折半插入排序法

最近需要使用到一些排序算法&#xff0c;今天主要使针对直接插入排序和折半插入排序进行讲解。 首先是直接插入排序&#xff0c;其排序过程主要是&#xff0c;针对A[a1,a2,a3,a4,a5....an]&#xff0c;从排序的序列头部起始位置开始&#xff0c;将其也就是a1视为只有一个元素的…

Android编译优化之Jetifier优化

本文字数&#xff1a;9048字 预计阅读时间&#xff1a;40分钟 在狐友项目的编译优化中&#xff0c;我们发现在 BuildAnalyzer 中有明确的 Warnings 提示&#xff0c;告知项目可以进行 Jetifier 优化。 Jetifier 是之前项目进行 AndroidX 迁移时引入的插件&#xff0c;它能辅助迁…

Azure Machine Learning - 提示工程简介

OpenAI的GPT-3、GPT-3.5和GPT-4模型基于用户输入的文本提示工作。有效的提示构造是使用这些模型的关键技能&#xff0c;涉及到配置模型权重以执行特定任务。这不仅是技术操作&#xff0c;更像是一种艺术&#xff0c;需要经验和直觉。本文旨在介绍适用于所有GPT模型的提示概念和…

diffuser为pipeline设置不用的scheduler

查看默认的schedulers&#xff1a; 使用默认的schedulers生成数据 查看默认scheduler的默认配置&#xff0c;定义了采样器中的相关参数&#xff0c;网上关于DDPM和DDIM的文章较多&#xff0c;可以先去看看这两种schedulers&#xff1a; 修改scheduler&#xff0c;可以用于…

13.二进制枚举练习题

文章目录 二进制枚举练习题[78. 子集](https://leetcode.cn/problems/subsets/)[77. 组合](https://leetcode.cn/problems/combinations/)[1286. 字母组合迭代器](https://leetcode.cn/problems/iterator-for-combination/)[2397. 被列覆盖的最多行数](https://leetcode.cn/pro…

CSS3 2D变形 过渡 动画

​​​​​ transform(2D变形)概述translate()平移scale()缩放skew()倾斜rotate()旋转transform-origin中心原点 CSS3 2D变形 3D变形 过渡 动画 在CSS3中&#xff0c;动画效果包括4个部分&#xff1a;变形&#xff08;transform&#xff09;、3D变形、过渡&#xff08;transit…

【Linux】cp问题,生产者消费者问题代码实现

文章目录 前言一、 BlockQueue.hpp&#xff08;阻塞队列&#xff09;二、main.cpp 前言 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯&#xff0c;而通过阻塞队列来进行通讯&#xff0c;所以生产者生产完数据之后不用…

计算机网络:网络层(无分类编址CIDR、计算题讲解)

带你快速通关期末 文章目录 前言一、无分类编址CIDR简介二、构成超网三、最长前缀匹配总结 前言 我们在前面知道了分类地址&#xff0c;但是分类地址又有很多缺陷&#xff1a; B类地址很快将分配完毕!路由表中的项目急剧增长! 一、无分类编址CIDR简介 无分类域间路由选择CI…

计算机网络:数据链路层(VLAN)

今天又学到一个知识&#xff0c;加油&#xff01; 目录 一、传统局域网的局限&#xff08;促进VLAN的诞生&#xff09; 二、VLAN简介 三、VLAN的实现 总结 一、传统局域网的局限&#xff08;促进VLAN的诞生&#xff09; 缺乏流量隔离:即使把组流量局域化道一个单一交换机中…

【Linux】初识命令行

为什么使用命令行&#xff1f; 大多数的计算机用户只是熟悉图形用户界面(GUI)&#xff0c;采用图形方式显示的用户操作界面。命令行界面(CLI)是一种通过文本输入来与计算机进行交互的方式&#xff0c;用来和计算机进行交流沟通的非常有效的方式&#xff0c;正像人类社会使用文…

【Windows】windows11右键默认显示更多选项的办法

Windows11系统的右键菜单显示&#xff0c;需要多点一次“显示更多选项”才能看到所有菜单内容&#xff0c;按下面步骤简单设置一下就能恢复成Windows经典的右键菜单显示。 1. 2.输入命令【reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a…

熬了一个通宵,把国内外的大模型都梳理完了!

大家好&#xff0c;大模型越来越多了&#xff0c;真的有点让人眼花缭乱。 为了让大家清晰地了解大模型&#xff0c;我熬了一个通宵把国内和国外的大模型进行了全面梳理&#xff0c;国内有189个&#xff0c;国外有20&#xff0c;同时包括大模型的来源机构、来源信息和分类等。 …

vue3+leaflet天地图开发

<script setup> import { onMounted, onUnmounted, ref } from "vue"; // todo 项目使用请放开 leaflet 引入 // import L from leaflet;const emit defineEmits(["mapLoad"]);var markers ref([]); const mapRef ref(); const marker ref(); co…

linux环境安装可操作图库语言Gremlin的图框架HugeGraph

原创/朱季谦 若你还没接触过图数据库&#xff0c;可能看到这个概念时&#xff0c;会比较蒙蔽。 图是什么&#xff1f;图数据库又是什么&#xff1f; 首先&#xff0c;在数据结构中&#xff0c;图是一种由顶点&#xff08;vertex&#xff09;集合及顶点间关系集合组成的一种非…

FRP内网映射家用服务器至公网访问

兄弟们&#xff0c;服务器到货了&#xff0c;后续与大家分享内容就用它了。我预装的操作系统是Centos8,首先要解决的是远程访问的问题。 【特别注意】下述的端口&#xff0c;记得在阿里云安全组配置中放开端口入规则&#xff01;&#xff01; 1. FRP服务器配置 之前我有购买…

如何将数据库导入MySQL的办法

在电脑cmd终端进行导入 首先找到MySQL中bin的位置 第一步&#xff1a;找到MySQL 第二步&#xff1a;进入MySQL 第三步&#xff1a;打开bin 第四步&#xff1a;输入cmd进入终端 第五步&#xff1a; 输入mysql -uroot -p 然后会弹出enter password&#xff1a; 输入你的密码…

Leetcode的AC指南 —— 链表:24. 两两交换链表中的节点

摘要&#xff1a; Leetcode的AC指南 —— 链表&#xff1a;24. 两两交换链表中的节点。题目介绍&#xff1a;给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能…

[计网01] 物理层 详细解析笔记,特性

计算机网络的物理层是网络协议栈中的第一层&#xff0c;负责传输原始的比特流&#xff08;bitstream&#xff09;通过物理媒介进行通信。物理层主要关注传输介质、信号的编码和调制、数据传输速率以及数据传输的物理连接等方面。 相关特性 机械特性&#xff08;Mechanical Ch…

Java已死!

许多开发者仍然认为 Java 与当今时代息息相关&#xff0c;看完本文&#xff0c;你会发现 Java 的影响力已经大幅减弱。实际上&#xff0c;Java 是一种濒临灭绝的编程语言。尽管 Java 一直是世界上使用最广泛、最受欢迎的编程语言之一&#xff0c;但它很快就会面临消亡的危险。 …

【C语言加油站】qsort函数的模拟实现

qsort函数的模拟实现 导言一、回调函数二、冒泡排序2.1 冒泡排序实现升序 三、qsort函数3.1 qsort函数的使用3.2 比较函数 四、通过冒泡排序模拟实现qsort函数4.1 任务需求4.2 函数参数4.3 函数定义与声明4.4 函数实现4.4.1 函数主体4.4.2 比较函数4.4.3 元素交换 4.5 my_qsort…
最新文章