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

实例:

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. TCP/UDP 报头格式

    参考:https://www.cnblogs.com/MRRAOBX/articles/4540228.html TCP: 源端口号  以及 目的端口号  32位 序列号  seq 确认号  ACK ...

  2. IT引导学生成长的文章链接(十二)

    链接:IT学子成长指导类文章链接(1)(2)(3) (4) (5)(6)(7)(8)(9)(10)(11) "IT学子成长指导"类我收藏过的好文(十二期:至2014年4月26日) ...

  3. jxl导出Excel

    首先先在自己工程中导入jxl的jar包: 疯狂google后找到一段别人的导出excel方法,先备份于下面: import java.io.File; import java.io.FileOutpu ...

  4. poj 3390 Print Words in Lines 动态规划

    意甲冠军: 给n每行长度和字符可放置最大数量字m,每一行产生值至(m-x)^2,x是一个字符上线人数(包含空话之间格).为了让所有的完成,产生的话值最小和. 分析: 动态规划非常重要的就是状态的定义, ...

  5. Kitto2 now with free opensource Kide2 since September 2017(提供Web解决方案,大概是觉得Mobile开发快差不多了)

    Kitto2 is a tool for data-driven web application Development. It allows to create Rich Internet Appl ...

  6. 初次使用glog

    一.安装配置 1.简单介绍 google 出的一个C++轻量级日志库,支持下面功能: ◆ 參数设置,以命令行參数的方式设置标志參数来控制日志记录行为: ◆ 严重性分级,依据日志严重性分级记录日志: ◆ ...

  7. MySQL更改表的存储引擎

    MySQL它提供了多种数据库存储引擎,存储引擎负责MySQL存储和检索数据的数据库.不同的存储引擎具有不同的特性,能须要将一个已经存在的表的存储引擎转换成另外的一个存储引擎.有非常多方法能够完毕这样的 ...

  8. 怎样从一名程序员过度到项目经理(整理自csdn论坛) 选择自 whoopee 的 Blog

    1.从程序员到PM,是一条脱变的路,事实上程序员走的路最终不应该是项目经理.首先有一点需要明白的就是,一定规模的项目中,项目经理不需要太懂技术,他可以是一知半解.项目经理的任务不是在技术方面,技术相关 ...

  9. Stream转Byte数组

    //获得当前文件目录 string rootPath = Directory.GetCurrentDirectory(); string path = rootPath + "Your Fi ...

  10. 数组/LINQ/List/ObservableCollection

    private static void AddIndustryTypes(sectorCode[] result) { var industryTypes = (from t in result se ...