热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java并发编程系列之六:深入理解ThreadLocal

引言无论实际项目实战还是面试,ThreadLocal都是一个绕不开的话题,本文主要从源码角度和大家一起探讨下ThreadLocal的神秘面纱。Thr

引言

无论实际项目实战还是面试,ThreadLocal都是一个绕不开的话题,本文主要从源码角度和大家一起探讨下ThreadLocal的神秘面纱。

  • ThreadLocal是什么?它能干什么?
  • ThreadLocal源码分析
  • 总结



一、ThreadLocal是什么?它能干什么?

ThreadLocal 是一个线程的本地变量, 也就意味着这个变量是线程独有的,是不能与其他线程共享的,它并不是解决多线程共享变量的问题。

所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以说ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。

ThreadLocal的思想就是用空间换时间,使各线程都能访问属于自己这一份的变量副本,变量值不互相干扰,减少同一个线程内的多个函数或者组件之间一些公共变量传递的复杂度。

在这里插入图片描述



二、ThreadLocal源码分析

1、ThreadLocalMap解析
ThreadLocal内部定义了一个ThreadLocalMap的内部类,ThreadLocalMap实际利用Entry来实现key-value的存储,如下所示:

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 &#61; v;}}...
}

ThreadLocalMap是实现线程隔离机制的关键&#xff0c;从以上代码可以看出Entrykey就是ThreadLocal&#xff0c;而value就是值。同时&#xff0c;Entry也继承WeakReference&#xff0c;所以说Entry所对应key&#xff08;ThreadLocal实例&#xff09;的引用为一个弱引用。

我们主要来看下核心的getEntry()set(ThreadLocal> key, Object value)方法

private Entry getEntry(ThreadLocal<?> key) {int i &#61; key.threadLocalHashCode & (table.length - 1);Entry e &#61; table[i];if (e !&#61; null && e.get() &#61;&#61; key)return e;elsereturn getEntryAfterMiss(key, i, e);}

private void set(ThreadLocal<?> key, Object value) {// We don&#39;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 &#61; table;int len &#61; tab.length;// 根据 ThreadLocal 的散列值&#xff0c;查找对应元素在数组中的位置int i &#61; key.threadLocalHashCode & (len-1);// 采用“线性探测法”&#xff0c;寻找合适位置for (Entry e &#61; tab[i];e !&#61; null;e &#61; tab[i &#61; nextIndex(i, len)]) {ThreadLocal<?> k &#61; e.get();// 若key 存在&#xff0c;直接覆盖if (k &#61;&#61; key) {e.value &#61; value;return;}// key &#61;&#61; null&#xff0c;但是存在值&#xff08;因为此处的e !&#61; null&#xff09;&#xff0c;说明之前的ThreadLocal对象已经被回收了if (k &#61;&#61; null) {// 用新元素替换陈旧的元素replaceStaleEntry(key, value, i);return;}}//创建新元素tab[i] &#61; new Entry(key, value);int sz &#61; &#43;&#43;size;// 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值&#xff0c;则进行 rehashif (!cleanSomeSlots(i, sz) && sz >&#61; threshold)rehash();}

2、核心方法解析
(1) get()
返回此线程局部变量的当前线程副本中的值

public T get() {//获取当前线程Thread t &#61; Thread.currentThread();//获取当前线程的成员变量 threadLocalMapThreadLocalMap map &#61; getMap(t);if (map !&#61; null) {// 从当前线程的ThreadLocalMap获取相对应的EntryThreadLocalMap.Entry e &#61; map.getEntry(this);if (e !&#61; null) {&#64;SuppressWarnings("unchecked")// 获取目标值T result &#61; (T)e.value;return result;}}return setInitialValue();}

首先通过当前线程获取所对应的成员变量ThreadLocalMap&#xff0c;然后通过ThreadLocalMap获取当前ThreadLocalEntry&#xff0c;最后通过所获取的Entry获取目标值result

(2) set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。

public void set(T value) {Thread t &#61; Thread.currentThread();ThreadLocalMap map &#61; getMap(t);if (map !&#61; null)map.set(this, value);elsecreateMap(t, value);}

获取当前线程所对应的ThreadLocalMap&#xff0c;如果不为空&#xff0c;则调用ThreadLocalMapset()方法&#xff0c;key就是当前ThreadLocal&#xff0c;如果不存在&#xff0c;则调用createMap()方法新建一个。

void createMap(Thread t, T firstValue) {t.threadLocals &#61; new ThreadLocalMap(this, firstValue);}

(3) initialValue()
返回此线程局部变量的当前线程的初始值。

protected T initialValue() {return null;}

该方法定义为protected级别且返回为null&#xff0c;需要其子类实现其功能&#xff0c;所以我们在使用ThreadLocal的时候一般都应该覆盖该方法。该方法不能显示调用&#xff0c;只有在第一次调用get()或者set()方法时才会被执行&#xff0c;并且仅执行1次。

(4) remove()
移除此线程局部变量当前线程的值。

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



三、总结

1、多个线程去获取一个共享变量时&#xff0c;要求获取的是这个变量的初始值的副本。每个线程存储这个变量的副本&#xff0c;对这个变量副本的改变不会影响变量本身。适用于多个线程依赖不同变量值完成操作的场景。比如&#xff1a;

  • 多数据源的切换
  • spring声明式事务

2、将ThreadLocal设置成private static的&#xff0c;这样ThreadLocal会尽量和线程本身一起回收。


推荐阅读
  • 本文探讨了Java中有效停止线程的多种方法,包括使用标志位、中断机制及处理阻塞I/O操作等,旨在帮助开发者避免使用已废弃的危险方法,确保线程安全和程序稳定性。 ... [详细]
  • 深入解析mt_allocator内存分配器(二):多线程与单线程场景下的实现
    本文详细介绍了mt_allocator内存分配器在多线程和单线程环境下的实现机制。该分配器以2的幂次方字节为单位分配内存,支持灵活的配置和高效的性能。文章分为内存池特性描述、内存池实现、单线程内存池实现、内存池策略类实现及多线程内存池实现等部分,深入探讨了内存池的初始化、内存分配与回收的具体实现。 ... [详细]
  • 本文探讨了Android系统中联系人数据库的设计,特别是AbstractContactsProvider类的作用与实现。文章提供了对源代码的详细分析,并解释了该类如何支持跨数据库操作及事务处理。源代码可从官方Android网站下载。 ... [详细]
  • Java高级工程师学习路径及面试准备指南
    本文基于一位朋友的PDF面试经验整理,涵盖了Java高级工程师所需掌握的核心知识点,包括数据结构与算法、计算机网络、数据库、操作系统等多个方面,并提供了详细的参考资料和学习建议。 ... [详细]
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 问题描述现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能;在实际开发过程中 ... [详细]
  • 深入理解Java SE 8新特性:Lambda表达式与函数式编程
    本文作为‘Java SE 8新特性概览’系列的一部分,将详细探讨Lambda表达式。通过多种示例,我们将展示Lambda表达式的不同应用场景,并解释编译器如何处理这些表达式。 ... [详细]
  • 本文详细介绍了使用Java语言来测量程序运行时间的方法,包括代码示例和实现步骤,旨在帮助开发者更好地理解和应用时间测量技术。 ... [详细]
  • 基于OpenCV的小型图像检索系统开发指南
    本文详细介绍了如何利用OpenCV构建一个高效的小型图像检索系统,涵盖从图像特征提取、视觉词汇表构建到图像数据库创建及在线检索的全过程。 ... [详细]
  • 本文详细记录了一位Java程序员在Lazada的面试经历,涵盖同步机制、JVM调优、Redis应用、线程池配置、Spring框架特性等多个技术点,以及高级面试中的设计问题和解决方案。 ... [详细]
  • Java多线程售票案例分析
    本文通过一个售票系统的实例,深入探讨了Java中的多线程技术及其在资源共享和并发控制中的应用。售票过程涉及查询、收款、找零和出票等多个步骤,其中对总票数的管理尤为关键。 ... [详细]
  • 本文回顾了作者在求职阿里和腾讯实习生过程中,从最初的迷茫到最后成功获得Offer的心路历程。文中不仅分享了个人的面试经历,还提供了宝贵的面试准备建议和技巧。 ... [详细]
  • 春季职场跃迁指南:如何高效利用金三银四跳槽季
    随着每年的‘金三银四’跳槽高峰期的到来,许多职场人士都开始考虑是否应该寻找新的职业机会。本文将探讨如何制定有效的职业规划、撰写吸引人的简历以及掌握面试技巧,助您在这关键时期成功实现职场跃迁。 ... [详细]
  • 本文探讨了Java中线程的多种终止方式及其状态转换,提供了关于如何安全有效地终止线程的指导。 ... [详细]
  • 深入探讨:Actor模型如何解决并发与分布式计算难题
    在现代软件开发中,高并发和分布式系统的设计面临着诸多挑战。本文基于Akka最新文档,详细探讨了Actor模型如何有效地解决这些挑战,并提供了对并发和分布式计算的新视角。 ... [详细]
author-avatar
chnger
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有