深入浅出的学习Java ThreadLocal

前言

ThreadLocal为变量在每个线程中都创建了一个副本,所以每个线程可以访问自己内部的副本变量,不同线程之间不会互相干扰。本文会基于实际场景介绍ThreadLocal如何使用以及内部实现机制。

应用场景

Parameter对象的数据需要在多个模块中使用,如果采用参数传递的方式,显然会增加模块之间的耦合性。先看看用ThreadLocal是如何实现模块间共享数据的。

class Parameter {
 private static ThreadLocal<Parameter> _parameter= new ThreadLocal<>();
 public static Parameter init() {
 _parameter.set(new Parameter());
 }
 public static Parameter get() {
 _parameter.get();
 }
 ...省略变量声明
}
  1. 在模块A中通过Parameter.init初始化。
  2. 在模块B或模块C中通过Parameter.get方法可以获得同一线程中模块A已经初始化的Parameter对象。

实现原理

从线程Thread的角度来看,每个线程内部都会持有一个对ThreadLocalMap实例的引用,ThreadLocalMap实例相当于线程的局部变量空间,存储着线程各自的数据,具体如下:

Entry

Entry继承自WeakReference类,是存储线程私有变量的数据结构。ThreadLocal实例作为引用,意味着如果ThreadLocal实例为null,就可以从table中删除对应的Entry。

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

ThreadLocalMap

内部使用table数组存储Entry,默认大小INITIAL_CAPACITY(16),先介绍几个参数:

  • size:table中元素的数量。
  • threshold:table大小的2/3,当size >= threshold时,遍历table并删除key为null的元素,如果删除后size >= threshold*3/4时,需要对table进行扩容。

ThreadLocal.set() 实现

public void set(T value) {
 Thread t = Thread.currentThread();
 ThreadLocalMap map = getMap(t);
 if (map != null)
 map.set(this, value);
 else
 createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
 return t.threadLocals;
}

从上面代码中看出来:

  1. 从当前线程Thread中获取ThreadLocalMap实例。
  2. ThreadLocal实例和value封装成Entry。

接下去看看Entry存入table数组如何实现的:

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)]) {
 ThreadLocal<?> k = e.get();
 if (k == key) {
 e.value = value;
 return;
 }
 if (k == null) {
 replaceStaleEntry(key, value, i);
 return;
 }
 }
 tab[i] = new Entry(key, value);
 int sz = ++size;
 if (!cleanSomeSlots(i, sz) && sz >= threshold)
 rehash();
}

1.通过ThreadLocal的nextHashCode方法生成hash值。

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

从nextHashCode方法可以看出,ThreadLocal每实例化一次,其hash值就原子增加HASH_INCREMENT。

2.通过 hash & (len -1) 定位到table的位置i,假设table中i位置的元素为f。

3.如果f != null,假设f中的引用为k:

  • 如果k和当前ThreadLocal实例一致,则修改value值,返回。
  • 如果k为null,说明这个f已经是stale(陈旧的)的元素。调用replaceStaleEntry方法删除table中所有陈旧的元素(即entry的引用为null)并插入新元素,返回。
  • 否则通过nextIndex方法找到下一个元素f,继续进行步骤3。

4.如果f == null,则把Entry加入到table的i位置中。

5.通过cleanSomeSlots删除陈旧的元素,如果table中没有元素删除,需判断当前情况下是否要进行扩容。

table扩容

如果table中的元素数量达到阈值threshold的3/4,会进行扩容操作,过程很简单:

private void resize() {
 Entry[] oldTab = table;
 int oldLen = oldTab.length;
 int newLen = oldLen * 2;
 Entry[] newTab = new Entry[newLen];
 int count = 0;
 for (int j = 0; j < oldLen; ++j) {
 Entry e = oldTab[j];
 if (e != null) {
 ThreadLocal<?> k = e.get();
 if (k == null) {
 e.value = null; // Help the GC
 } else {
 int h = k.threadLocalHashCode & (newLen - 1);
 while (newTab[h] != null)
 h = nextIndex(h, newLen);
 newTab[h] = e;
 count++;
 }
 }
 }
 setThreshold(newLen);
 size = count;
 table = newTab;
}
  1. 新建新的数组newTab,大小为原来的2倍。
  2. 复制table的元素到newTab,忽略陈旧的元素,假设table中的元素e需要复制到newTab的i位置,如果i位置存在元素,则找下一个空位置进行插入。

ThreadLocal.get() 实现

public T get() {
 Thread t = Thread.currentThread();
 ThreadLocalMap map = getMap(t);
 if (map != null) {
 ThreadLocalMap.Entry e = map.getEntry(this);
 if (e != null) {
 @SuppressWarnings("unchecked")
 T result = (T)e.value;
 return result;
 }
 }
 return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
 int i = key.threadLocalHashCode & (table.length - 1);
 Entry e = table[i];
 if (e != null && e.get() == key)
 return e;
 else
 return getEntryAfterMiss(key, i, e);
}

获取当前的线程的threadLocals。

  1. 如果threadLocals不为null,则通过ThreadLocalMap.getEntry方法找到对应的entry,如果其引用和当前key一致,则直接返回,否则在table剩下的元素中继续匹配。
  2. 如果threadLocals为null,则通过setInitialValue方法初始化,并返回。
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
 Entry[] tab = table;
 int len = tab.length;
 while (e != null) {
 ThreadLocal<?> k = e.get();
 if (k == key)
 return e;
 if (k == null)
 expungeStaleEntry(i);
 else
 i = nextIndex(i, len);
 e = tab[i];
 }
 return null;
}

总结

希望通过本文的介绍,大家可以对ThreadLocal有一个更加直观清晰的认识。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我们!

(0)

相关推荐

  • Java多线程编程之ThreadLocal线程范围内的共享变量

    模拟ThreadLocal类实现:线程范围内的共享变量,每个线程只能访问他自己的,不能访问别的线程. package com.ljq.test.thread; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * 线程范围内的共享变量 * * 三个模块共享数据,主线程模块和AB模块 * * @author Administrator * */ public class ThreadScopeS

  • 简单分析Java线程编程中ThreadLocal类的使用

    一.概述   ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突.   从线程的角度看,每个线程都保持一个对

  • Java 并发编程之ThreadLocal详解及实例

    Java 理解 ThreadLocal 摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态.进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤. 一. 对 ThreadLocal 的理解 1). ThreadLocal 概述 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制

  • 实例详解Java中ThreadLocal内存泄露

    案例与分析 问题背景 在 Tomcat 中,下面的代码都在 webapp 内,会导致WebappClassLoader泄漏,无法被回收. public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class MyThreadLocal extends ThreadLocal<MyCount

  • java 中ThreadLocal 的正确用法

    java 中ThreadLocal 的正确用法 用法一:在关联数据类中创建private static ThreadLocalThreaLocal的JDK文档中说明:ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.如果我们希望通过某个类将状态(例如用户ID.事务ID)与线程关联起来,那么通常在这个类中定义private s

  • Java ThreadLocal 线程安全问题解决方案

    一.线程安全问题产生的原因 线程安全问题都是由全局变量及静态变量引起的 二.线程安全问题 SimpleDateFormate sdf = new SimpleDateFormat();使用sdf.parse(dateStr);sdf.format(date);在sdf内有一个对Caleadar对象的引用,在源码sdf.parse(dateStr);源码中calendar.clear();和calendar.getTime(); // 获取calendar的时间 如果 线程A 调用了 sdf.pa

  • java 中ThreadLocal本地线程和同步机制的比较

    ThreadLocal的设计 首先看看ThreadLocal的接口: Object get() ; // 返回当前线程的线程局部变量副本 protected Object initialValue(); // 返回该线程局部变量的当前线程的初始值 void set(Object value); // 设置当前线程的线程局部变量副本的值 ThreadLocal有3个方法,其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的.该方法返回当

  • java 中ThreadLocal实例分析

    java  中ThreadLocal实例分析 从概念上理解,threadlocal使变量在多个线程中相互隔离实现线程安全,threadlocal包装的变量最终都专属于对应的每个线程,线程之间相互独立,用一个具体实现来说明: public interface Consumer { int consume(); } public class ComsumeThread implements Runnable { private Consumer consumer; public ComsumeThr

  • 深入浅出的学习Java ThreadLocal

    前言 ThreadLocal为变量在每个线程中都创建了一个副本,所以每个线程可以访问自己内部的副本变量,不同线程之间不会互相干扰.本文会基于实际场景介绍ThreadLocal如何使用以及内部实现机制. 应用场景 Parameter对象的数据需要在多个模块中使用,如果采用参数传递的方式,显然会增加模块之间的耦合性.先看看用ThreadLocal是如何实现模块间共享数据的. class Parameter { private static ThreadLocal<Parameter> _param

  • 深入学习java ThreadLocal的源码知识

    简介 ThreadLocal是每个线程自己维护的一个存储对象的数据结构,线程间互不影响实现线程封闭.一般我们通过ThreadLocal对象的get/set方法存取对象. 源码分析 ThreadLocal的set方法源码如下 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 根据当前线程获得ThreadLocalMap对象 if (map != null)

  • java ThreadLocal使用案例详解

    本文借由并发环境下使用线程不安全的SimpleDateFormat优化案例,帮助大家理解ThreadLocal. 最近整理公司项目,发现不少写的比较糟糕的地方,比如下面这个: public class DateUtil { private final static SimpleDateFormat sdfyhm = new SimpleDateFormat( "yyyyMMdd"); public synchronized static Date parseymdhms(String

  • 深入浅出解析Java ThreadLocal原理

    目录 1.了解ThreadLocal 简介 使用 2.源码解析 – 探究实现思路 threadLocals变量与ThreadLocalMap set(T value) 方法 get() 方法 remove() 方法 实现思路总结 3.InheritableThreadLocal与继承性 ThreadLocal的不可继承性 InheritableThreadLocal实现继承性的源码剖析 如何理解这个继承性 总结 4.存在的内存泄露问题 使用强引用会如何? 使用弱引用会如何? set().get(

  • Java ThreadLocal类使用详解

    目录 前言 快速开始 ThreadLocal的原理 ThreadLocal相关类图 set get remove 小结 ThreadLocal内存泄露 为什么会出现内存泄漏? 为什么使用弱引用? 解决方法 总结 前言 这几天看<Java并发编程之美>的时候又遇到了ThradLocal这个类,不得不说,这个类在平时很多场景都遇得到,所以对其进行一个系统性的学习,然后再输出成这篇博客. 那么,什么是ThreadLocal呢? 我们都知道,多线程访问同一个共享变量很容易出现并发问题,特别是当多个线程

  • 学习Java设计模式之观察者模式

    观察者模式:对象间的一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象(被观察). 以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并发生相应的变化. 观察者模式有很多实现方式:该模式必须包含观察者和被观察对象两种角色.观察者和被观察者之间存在"观察"的逻辑关系,当被观察者发生改变的时候,观察者就会观察到这样的变化,发出相应的改变. /** * 观察者接口:观察者,需要用到观察者模式的类需实现此接口 */ public interface Observer { pu

  • 计算机二级考试java软件操作教程 教大家如何学习java

    Java并不难,只是包含的内容比较多.语言本身相当精练,但功能非常强大.为了能够更加有效地使用Java编制程序,你需要学习该语言所包含的库,其中的内容十分丰富.在本书中.你将依次了解语言如何运行以及如何应用它.学习这些内容的顺序经过精心地安排,通过一些相对简单.明了的过程,你就可以获得一定的专业知识,建立运用Java进行程序设计的信心.每一章都尽量避免使用你还没有学习到的东西.这样一来,你不能马上编写嵌入Weh网页的Java程序,但是这确实是一种诱人的想法,有点像冒险跳入深水学习游泳.一般说来,

  • 学习Java多线程之同步

    如果你的java基础较弱,或者不大了解java多线程请先看这篇文章<学习Java多线程之线程定义.状态和属性> 同步一直是java多线程的难点,在我们做android开发时也很少应用,但这并不是我们不熟悉同步的理由.希望这篇文章能使更多的人能够了解并且应用java的同步. 在多线程的应用中,两个或者两个以上的线程需要共享对同一个数据的存取.如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法,这种情况通常成为竞争条件. 竞争条件最容易理解的例子就是:比如火车卖票,火车票是一定的,

  • Java ThreadLocal类应用实战案例分析

    本文实例讲述了Java ThreadLocal类应用.分享给大家供大家参考,具体如下: 一 点睛 ThreadLocal,是Thread Local Variable(线程局部变量)的意思,也许将它命名为ThreadLocalVar更加合适. 线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突.从线程的角度看,就好像每一个线程都完全拥有该变量. ThreadLocal类的

  • Java ThreadLocal用法实例详解

    本文实例讲述了Java ThreadLocal用法.分享给大家供大家参考,具体如下: 目录 ThreadLocal的基本使用 ThreadLocal实现原理 源码分析(基于openjdk11) get方法: setInitialValue方法 getEntry方法 set方法 ThreadLocalMap的set方法 replaceStaleEntry方法 cleanSomeSlots方法 rehash方法 expungeStaleEntries方法 resize方法 ThreadLocal实现

随机推荐