Java多线程(4):ThreadLocal
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~
为了提高CPU的利用率,工程师们创造了多线程。但是线程们说:要有光!(为了减少线程创建(T1启动)和销毁(T3切换)的时间),于是工程师们又接着创造了线程池ThreadPool。就这样就可以了吗?——不,工程师们并不满足于此,他们不把自己创造出来的线程给扒个底朝天决不罢手。
有了线程关键字解决线程安全问题,有了线程池解决效率问题,那还有什么问题是可以需要被解决的呢?——还真被这帮疯子攻城狮给找到了!
当多个线程共享同一个资源的时候,为了保证线程安全,有时不得不给资源加锁,例如使用Synchronized关键字实现同步锁。这本质上其实是一种时间换空间的搞法——用单一资源让不同的线程依次访问,从而实现内容安全可控。就像这样:

但是,可以不可以反过来,将资源拷贝成多份副本的形式来同时访问,达到一种空间换时间的效果呢?当然可以,就像这样:

而这,就是ThreadLocal最核心的思想。
但这种方式在很多应用级开发的场景中用得真心不多,而且有些公司还禁止使用ThreadLocal,因为它搞不好还会带来一些负面影响。
其实,从拷贝若干副本这种功能来看,ThreadLocal是实现了在线程内部存储数据的能力的,而且相互之间还能通信。就像这样:

还是以代码的形式来解读一下ThreadLocal。有一个资源类Resource:
/**
* 资源类
*
* @author 湘王
*/
public class Resource {
private String name;
private String value; public Resource(String name, String value) {
super();
this.name = name;
this.value = value;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getValue() {
return value;
} public void setValue(String value) {
this.value = value;
}
}
分别有ResuorceUtils1、ResuorceUtils2和ResuorceUtils3分别以不同的方式来连接资源,那么看看效率如何。
/**
* 连接资源工具类,通过静态方式获得连接
*
* @author 湘王
*/
public class ResourceUtils1 {
// 定义一个静态连接资源
private static Resource resource = null;
// 获取连接资源
public static Resource getResource() {
if(resource == null) {
resource = new Resource("xiangwang", "123456");
}
return resource;
} // 关闭连接资源
public static void closeResource() {
if(resource != null) {
resource = null;
}
}
} /**
* 连接资源工具类,通过实例化方式获得连接
*
* @author 湘王
*/
public class ResourceUtils2 {
// 定义一个连接资源
private Resource resource = null;
// 获取连接资源
public Resource getResource() {
if(resource == null) {
resource = new Resource("xiangwang", "123456");
}
return resource;
} // 关闭连接资源
public void closeResource() {
if(resource != null) {
resource = null;
}
}
} /**
* 连接资源工具类,通过线程中的static Connection的副本方式获得连接
*
* @author 湘王
*/
public class ResourceUtils3 {
// 定义一个静态连接资源
private static Resource resource = null;
private static ThreadLocal<Resource> resourceContainer = new ThreadLocal<Resource>();
// 获取连接资源
public static Resource getResource() {
synchronized(ResourceManager.class) {
resource = resourceContainer.get();
if(resource == null) {
resource = new Resource("xiangwang", "123456");
resourceContainer.set(resource);
}
return resource;
}
} // 关闭连接资源
public static void closeResource() {
if(resource != null) {
resource = null;
resourceContainer.remove();
}
}
} /**
* 连接资源管理类
*
* @author 湘王
*/
public class ResourceManager {
public void insert() {
// 获取连接
// System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + resource);
} public void delete() {
// 获取连接
// System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + resource);
} public void update() {
// 获取连接
// System.out.println("Dao.update()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.update()-->" + Thread.currentThread().getName() + resource);
} public void select() {
// 获取连接
// System.out.println("Dao.select()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.select()-->" + Thread.currentThread().getName() + resource);
} public void close() {
ResourceUtils3.closeResource();
} public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
ResourceManager rm = new ResourceManager();
@Override
public void run() {
rm.insert();
rm.delete();
rm.update();
rm.select();
rm.close();
}
}).start();
}
}
}
执行ResourceManager类中的main()方法后,可以清楚地看到:
第一种静态方式:大部分资源都能复用,但毫无规律;
第二种实例方式:即使是同一个线程,资源实例也不一样;
第三种ThreadLocal静态方式:相同的线程有相同的实例。
结论是:ThreadLocal实现了线程的资源复用。
也可以通过画图的方式来看清楚三者之间的不同:
这是静态方式下的资源管理:

这是实例方式下的资源管理:

这是ThreadLocal静态方式下的资源管理:

理解了之后,再来看一个数据传递的例子,也就是ThreadLocal实现线程间通信的例子:
/**
* 数据传递
*
* @author 湘王
*/
public class DataDeliver {
static class Data1 {
public void process() {
Resource resource = new Resource("xiangwang", "123456");
//将对象存储到ThreadLocal
ResourceContextHolder.holder.set(resource);
new Data2().process();
}
} static class Data2 {
public void process() {
Resource resource = ResourceContextHolder.holder.get();
System.out.println("Data2拿到数据: " + resource.getName());
new Data3().process();
}
} static class Data3 {
public void process() {
Resource resource = ResourceContextHolder.holder.get();
System.out.println("Data3拿到数据: " + resource.getName());
}
} static class ResourceContextHolder {
public static ThreadLocal<Resource> holder = new ThreadLocal<>();
} public static void main(String[] args) {
new Data1().process();
}
}
运行代码之后,可以看到Data1的数据都被Data2和Data3拿到了,就像这样:

ThreadLocal在实际应用级开发中较少使用,因为容易造成OOM:
1、由于ThreadLocal是一个弱引用(WeakReference<ThreadLocal<?>>),因此会很容易被GC回收;
2、但ThreadLocalMap的生命周期和Thread相同,这就会造成当key=null时,value却还存在,造成内存泄漏。所以,使用完ThreadLocal后需要显式调用remove操作(但很多码农不知道这一点)。
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~
Java多线程(4):ThreadLocal的更多相关文章
- Java多线程之 ThreadLocal
一.什么是ThreadLocal? 顾名思义它是local variable(线程局部变量).它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副 ...
- Java多线程基础-ThreadLocal
感谢原文作者:Yuicon 原文链接:https://segmentfault.com/a/1190000016705955 序 在多线程环境下,访问非线程安全的变量时必须进行线程同步,例如使用 sy ...
- java 多线程(threadlocal)
package com.example; import java.util.Random; public class App { public static class MyRunnable1 imp ...
- java多线程学习-ThreadLocal
为了凑字,把oracle文档里介绍ThreadLocal抄过来 public class ThreadLocal<T> extends Object This class provides ...
- Java多线程:ThreadLocal
一.ThreadLocal基础知识 ThreadLocal是线程的一个本地化对象,或者说是局部变量.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的 ...
- java 多线程 day06 threadLocal
import java.util.HashMap;import java.util.Map;import java.util.Random; /** * Created by chengtao on ...
- java 多线程 :ThreadLocal 共享变量多线程不同值方案;InheritableThreadLocal变量子线程中自定义值,孙线程可继承
ThreadLocal类的使用 变量值的共享可以使用public static变量的形式,所有的线程都是用同一个public static变量.如果想实现每一个线程都有自己的值.该变量可通过Thr ...
- java多线程--------深入分析 ThreadLocal 内存泄漏问题
前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...
- java 多线程 一个博客
http://blog.csdn.net/a352193394/article/category/2563875 Java多线程之~~~线程安全容器的非阻塞容器 在并发编程中,会经常遇到使用容器.但是 ...
- Java多线程系列——从菜鸟到入门
持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...
随机推荐
- Luogu2290 [HNOI2004]树的计数 (组合计数,prufer编码)
这不prufer编码吗,防爆long long就行了啊 #include <iostream> #include <cstdio> #include <cstring&g ...
- 大家都能看得懂的源码 - 如何封装 cookie/localStorage/sessionStorage hook?
本文是深入浅出 ahooks 源码系列文章的第九篇,该系列已整理成文档-地址.觉得还不错,给个 star 支持一下哈,Thanks. 今天来看看 ahooks 是怎么封装 cookie/localSt ...
- Excelize 2.3.2 发布,Go 语言 Excel 文档基础库,2021 年首个更新
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准.可以使用它来读取.写入由 Microsoft Exc ...
- JZM 的套题(暴力,DP)
J Z M \rm JZM JZM 即将奔赴 N O I \rm NOI NOI 考场,为了让同学们赶上自己的千分之一水平,刻意出了两道水题给同学们练练. 1.曾经的代码 J Z M \rm JZM ...
- JavaScript设计模式及代码实现——单例模式
单例模式 1 定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 2 应用时机 当一个类的实例被频繁使用,如果重复创建这个实例,会无端消耗资源.比如 dialog 弹窗会被全局重复使用 业务 ...
- 牛客小白月赛51-C-E
C-零一题 题意: 每次可以选择两个相邻且相同的字符,将他们删除,在无数次操作后,字符串的长度变为n,问能否构造出原来的字符串,不能输出-1 题解: 很明显,最后无法再操作时,这个字符串一定是01相交 ...
- KingbaseESV8R3对于order by null列的处理
背景 客户遇到一个现象order by 列是null的时候,最后一行总是显示相同的值.不要问我为什么对空值排序,意义何在?客户代码如此,客户不愿意改代码,作为dba大家都懂的.客户认为即使对null排 ...
- React Native 入门 调试项目
不管时用哪种语言,哪种框架,调试永远都是一个避不开的话题 为我们提供了远程调试的功能,而这个功能需要Chrome浏览器的配合. 1. 首先浏览器一定要安装好React Developer Tool 插 ...
- TextView 中文本内容换行
TextView 中文本内容换行 首先如图所示,我的第一栏围城的书名和书的介绍是在同一行 但是我想让书名和书的介绍分开个站一行 这时我只要在我的数组文本中的文本用 "\n" 就可以 ...
- Vim使用技巧(持续更新)
好记性不如烂笔头,在这里记录一些Vim使用技巧 vim配置 "拷贝同步到系统剪切板" set clipboard=unnamed "显示行号" set nu & ...