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

实例:

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. 1.通过模板创建MAP版本项目

    1.选择mpa+ef+module-zero 取名字 2.用vs打开项目后,在解决方案上右键 还原nuget包 3.打开程序包管理器控制台,选择以EntityFramework结尾的项目,并执行upd ...

  2. Linux性能测试 mpstat命令

    mpstat是MultiProcessor Statistics的缩写,是实时系统监控工具.其报告与CPU的一些统计信息,这些信息存放在/proc/stat文件中.在多CPUs系统里,其不但能查看所有 ...

  3. [Songqw.Net 基础]WPF插件化中同步Style

    原文:[Songqw.Net 基础]WPF插件化中同步Style 版权声明:本文为博主原创文章,未经博主允许可以随意转载 https://blog.csdn.net/songqingwei1988/a ...

  4. HDU5187 zhx&#39;s contest(计数问题)

    主题链接: http://acm.hdu.edu.cn/showproblem.php?pid=5187 题意: 从1~n,有多少种排列 使得 a1~ai 满足单调递增或者单调递减. ai~an 满足 ...

  5. 自定义 DependencyProperty 与 RoutedEvent

    原文:自定义 DependencyProperty 与 RoutedEvent //自定义依赖属性 class MyBook : DependencyObject//依赖属性必须派生自Dependen ...

  6. docker include not found: networks

    启动clickhouse的docker镜像时,出现了以下错误 include not found: networks google之后发现是因为可能不支持ipv6导致的解决方法 就是通过设置 /etc ...

  7. LeapMotion Demo2

    原文:LeapMotion Demo2    官方doc有四个手势,最近尝试实现对握拳的识别,并能在我的程序界面上体现出来.    调试过程较为繁琐,幸好最终效果还差强人意! 首先看看我的效果图:   ...

  8. 【转载】Docker部署nginx并修改配置文件

    docker 部署个nginx docker run \ --name nginx-health-web-pc \ -d -p 6800:80 \ -v /usr/docker/nginx/html: ...

  9. DataTemplate

    DataTemplate作用是布局+数据绑定 使用DataTemplate 同时完成样式布局和数据绑定 <Window.Resources> <DataTemplate x:Key= ...

  10. WPF编游戏系列 之二 图标效果

    原文:WPF编游戏系列 之二 图标效果        本篇将要实现图标的两个效果:1. 显示图标标签,2. 图标模糊效果.在上一篇中提到Image没有HTML <img>的Title属性( ...