线程本地存储是一个自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。简单来说,就是对于某个变量,针对不同的线程存储不同的值。

实例:

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; /**
* @Description
* @Author KToTo
* @Date 2019/3/18 22:22
**/
public class ThreadLoaclVariableHolder {
//创建一个全局的ThreadLocal对象
private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
private Random random = new Random(47);
//初始化方法,此处的Random相当于共享变量,为了使演示效果明显,
//故将该初始化方法同步
protected synchronized Integer initialValue() {
return random.nextInt(1000);
}
}; //提供公有的递增和获取方法
public static void increment() {
value.set(value.get() + 1);
} public static int get() {
return value.get();
} public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
pool.execute(new Accessor(i));
}
//与Thread.sleep(long mils)方法功能一致
TimeUnit.SECONDS.sleep(3);
//立即关闭线程池,不熟悉的读者可以自行百度shutdown和shutdownNow的区别
pool.shutdownNow();
}
} class Accessor implements Runnable {
private final int id; public Accessor(int id) {
this.id = id;
} @Override
public void run() {
//响应中断
while (!Thread.currentThread().isInterrupted()) {
ThreadLoaclVariableHolder.increment();
System.out.println(this);
//对于多核处理器,此方法可以省略
Thread.yield();
}
} @Override
public String toString() {
return "Accessor{" +
"id=" + id + ":" + ThreadLoaclVariableHolder.get() +
'}';
}
}

原理分析

  • 从概念上来看,你可以将ThreadLocal<T>视为包含了Map<Thread, T>对象,其中保存了特定于该线程的值,但是实际上并非如此,这些特定于线程的值其实是保存在Thread对象中的,当线程终止后,这些值会作为垃圾回收。
  • 首先简单概括一下,当我们使用ThreadLocal的时候,我们想要的效果是针对该共享变量,每个线程中访问该变量的值是不一样的,而且是互不影响的。而ThreadLocal可以达到这一效果,其实是在每一个Thread对象中都有一个ThreadLocalMap的实例对象,当当前线程对改ThreadLocal进行初始化的时候,会以ThreadLocal的对象为key,值为value,存放到当前线程对象中的ThreadLocalMap。
  • 在上述的示例中,我们可以看到我们直接使用了ThreadLocal的Get方法,而不是先Set然后Get,下面让我们从Get方法入手,来了解一下ThreadLocal的实现

  

public T get() {
Thread t = Thread.currentThread();
//获取当前线程Thread对象中存放的ThreadLocalMap。
ThreadLocalMap map = getMap(t);
//判断当前对象是否存在本地线程对象
if (map != null) {
//判断当前线程对象中的threadLocals是否存储当前对象(注意这个this)的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果上述两个判断均不满足,则进行初始化
return setInitialValue();
} ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//关于ThreadLocalMap类的定义请参考源码
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
/**
*调用重写的initialValue()方法获取初始值,
*然后将当前ThreadLocal的初始化值添加到Thread对象的threadLocals变量中。
**/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
} private void set(ThreadLocal<?> key, Object value) { // We don'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 = 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();
}

ThreadLocal的副作用

  • 脏数据,线程复用会导致产生脏数据。由于线程池会重用Thread对象,那么与Thread绑定的静态属性ThreadLocal变量也会被复用
  • 内存泄露,当ThreadLocal为静态属性时,就不可以寄希望于ThreadLocal对象失去引用时,触发弱引用机制来回收就不现实了。
  • 针对上述两种问题的解决办法就是在每次使用完ThreadLocal之后,必须要及时的调用remove()方法清理。

参考:《Thinking In Java》、《Java并发编程实战》、《码出高效》

本地线程-ThreadLocal的更多相关文章

  1. Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic

    Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...

  2. 线程本地变量ThreadLocal

    一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...

  3. ThreadLocal本地线程变量的理解

     一般的Web应用划分为展现层.服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用.在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程.       ...

  4. Java并发(二十):线程本地变量ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类. 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的 ...

  5. Java 类 ThreadLocal 本地线程变量

    前言:工作中将要使用ThreadLocal,先学习总结一波.有不对的地方欢迎评论指出. 定义 ThreadLocal并不是一个Thread,而是Thread的局部变量.这些变量不同于它们的普通对应物, ...

  6. Flask中的ThreadLocal本地线程,上下文管理

    先说一下和flask没有关系的: 我们都知道线程是由进程创建出来的,CPU实际执行的也是线程,那么线程其实是没有自己独有的内存空间的,所有的线程共享进程的资源和空间,共享就会有冲突,对于多线程对同一块 ...

  7. ThreadLocal = 本地线程?

    一.定义 ThreadLocal是JDK包提供的,从名字来看,ThreadLocal意思就是本地线程的意思. 1.1 是什么? 要想知道他是个啥,我们看看ThreadLocal的源码(基于JDK 1. ...

  8. Filter(过滤器)、ThreadLocal(本地线程)、Listener(监听器)

    Filter(过滤器) Filter过滤器它的作用是:拦截请求,过滤响应. 过滤器链 1)执行的顺序依次是: A B C Demo03 C2 B2 A2 2)如果采取的是注解的方式进行配置,那么过滤器 ...

  9. 3、flask之基于DBUtils实现数据库连接池、本地线程、上下文

    本篇导航: 数据库连接池 本地线程 上下文管理 面向对象部分知识点解析 1.子类继承父类__init__的三种方式 class Dog(Animal): #子类 派生类 def __init__(se ...

随机推荐

  1. 狄拉克函数(Dirac delta function)

    1. 定义 δ(x)={∞0if x=0if x≠0 这样定义的目的在于使如下的积分式成立: ∫∞−∞δ(x)dx=1 2. 重要性质 sifting property ∫∞−∞f(x)δ(x−μ)d ...

  2. 理解 t-SNE (Python)

    t-SNE(t-distribution Stochastic Neighbor Embedding)是目前最为流行的高维数据的降维算法. t-SNE 成立的前提基于这样的一个假设:我们现实世界观察到 ...

  3. Win7 32bit下一个hadoop2.5.1源代码编译平台的搭建各种错误遇到

    从小白在安装hadoop困难和错误时遇到说起,同时,我们也希望能得到上帝的指示. 首先hadoop更新速度非常快,最新的是hadoop2.5.1,因此就介绍下在安装2.5.1时遇到的各种困难. 假设直 ...

  4. 让你编写的控件库在 XAML 中有一个统一的漂亮的命名空间(xmlns)和命名空间前缀

    原文 让你编写的控件库在 XAML 中有一个统一的漂亮的命名空间(xmlns)和命名空间前缀 在 WPF XAML 中使用自己定义的控件时,想必大家都能在 XAML 中编写出这个控件的命名空间了.然而 ...

  5. 并行编程OpenMP基础及简单示例

    OpenMP基本概念 OpenMP是一种用于共享内存并行系统的多线程程序设计方案,支持的编程语言包括C.C++和Fortran.OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的 ...

  6. WPF圆形环绕的Loading动画

    原文:WPF圆形环绕的Loading动画 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/yangyisen0713/article/details/ ...

  7. TestNg依靠先进的采用强制的依赖,并依赖序列的------TestNg依赖于特定的解释(两)

    原创文章,版权所有所有,转载,归因:http://blog.csdn.net/wanghantong TestNg使用dependsOnGroups属性来进行依赖測试, 測试方法依赖于某个或某些方法, ...

  8. 关于hibernate一对一配置

    实体类关系 一对一 一对多 多对一 多对多 Hibernate提供了两种映射一对一关联关系的方式:按照外键映射和按照主键映射.下面以员工账号和员工档案表为例,介绍这两种映射方式,并使用这两种映射方式分 ...

  9. MIME映射(程序映射)

    MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型.是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器 ...

  10. jquery ready和window onload区别

    window onload是指标签加载完成,并且标签资源加载完成: jquery ready是指标签加载完成,标签资源可能未加载完成 $(document).ready(function(){});= ...