Java多线程-ThreadLocal(六)
为了提高CPU的利用率,工程师们创造了多线程。但是线程们说:要有光!(为了减少线程创建(T1启动)和销毁(T3切换)的时间),于是工程师们又接着创造了线程池ThreadPool。就这样就可以了吗?——不,工程师们并不满足于此,他们不把自己创造出来的线程给扒个底朝天决不罢手。
有了线程关键字解决线程安全问题,有了线程池解决效率问题,那还有什么问题是可以需要被解决的呢?——还真被这帮疯子攻城狮给找到了!
当多个线程共享同一个资源的时候,为了保证线程安全,有时不得不给资源加锁,例如使用Synchronized关键字实现同步锁。这本质上其实是一种时间换空间的搞法——用单一资源让不同的线程依次访问,从而实现内容安全可控。就像这样:
但是,可以不可以反过来,将资源拷贝成多份副本的形式来同时访问,达到一种空间换时间的效果呢?当然可以,就像这样:
而这,就是ThreadLocal最核心的思想。
但这种方式在很多应用级开发的场景中用得真心不多,而且有些公司还禁止使用ThreadLocal,因为它搞不好还会带来一些负面影响。
其实,从拷贝若干副本这种功能来看,ThreadLocal是实现了在线程内部存储数据的能力的,而且相互之间还能通信。就像这样:
还是以代码的形式来解读一下ThreadLocal。有一个资源类Resource:
/**
* 资源类
*/
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分别以不同的方式来连接资源,那么看看效率如何。
/**
* 连接资源工具类,通过静态方式获得连接
*/
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;
}
}
} /**
* 连接资源工具类,通过实例化方式获得连接
*/
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的副本方式获得连接
*/
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();
}
}
} /**
* 连接资源管理类
*/
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实现线程间通信的例子:
/**
* 数据传递
*/
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操作(但很多码农不知道这一点)。
Thread ThreadLocal ThreadLocalMap之间的关系:
1、Thread中有个属性用于存放ThreadLocalMap
2、ThreadLocalMap是ThreadLocal的静态内部类
3、ThreadLocalMap中保存的是Entry(键值对:键是ThreadLocal,值是自己设置的value)
Java多线程-ThreadLocal(六)的更多相关文章
- “全栈2019”Java多线程第六章:中断线程interrupt()方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java多线程——ThreadLocal类的原理和使用
Java多线程——ThreadLocal类的原理和使用 摘要:本文主要学习了ThreadLocal类的原理和使用. 概述 是什么 ThreadLocal可以用来维护一个变量,提供了一个ThreadLo ...
- [Java多线程]-ThreadLocal源码及原理的深入分析
ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. //-------------------------- ...
- java多线程系列(六)---线程池原理及其使用
线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...
- Java多线程核心技术(六)线程组与线程异常
本文应注重掌握如下知识点: 线程组的使用 如何切换线程状态 SimpleDataFormat 类与多线程的解决办法 如何处理线程的异常 1.线程的状态 线程对象在不同运行时期有不同的状态,状态信息就处 ...
- Java多线程ThreadLocal介绍
在Java多线程环境下ThreadLocal就像一家银行,每个线程就是银行里面的一个客户,每个客户独有一个保险箱来存放金钱,客户之间的金钱不影响. private static ThreadLocal ...
- java多线程系列六、线程池
一. 线程池简介 1. 线程池的概念: 线程池就是首先创建一些线程,它们的集合称为线程池. 2. 使用线程池的好处 a) 降低资源的消耗.使用线程池不用频繁的创建线程和销毁线程 b) 提高响应速度,任 ...
- Java多线程——ThreadLocal类
一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名 ...
- java多线程(六)线程控制类
1. 多线程控制类 为了保证多线程的三个特性,Java引入了很多线程控制机制,下面介绍其中常用的几种: l ThreadLocal l 原子类 l Lock类 l Volatile关键字 ...
- 深入理解Java多线程——ThreadLocal
目录 定义 API 场景分析 场景实验,观察Spring框架在多线程场景的执行情况 10000此请求,单线程 10000次请求,线程数加到100 对c的访问加锁 把c设为ThreadLocal 收集多 ...
随机推荐
- JSP常见错误以及解决方案
原作者为 RioTian@cnblogs, 本作品采用 CC 4.0 BY 进行许可,转载请注明出处. 本节我们分析一下常见的 JSP 错误信息,并给出解决方案.这些错误在实际开发中会经常遇到,所以有 ...
- SpringCloud学习 系列十、服务熔断与降级(1-简介)
系列导航 SpringCloud学习 系列一. 前言-为什么要学习微服务 SpringCloud学习 系列二. 简介 SpringCloud学习 系列三. 创建一个没有使用springCloud的服务 ...
- Vue 大屏可视化 铺满全屏
https://blog.csdn.net/u011097323/article/details/106728221?utm_medium=distribute.pc_aggpage_search_r ...
- NCC Mocha v0.10 发布, .NET 开发的基于 OpenTelemetry 的 APM 系统
目录 项目简介 项目进度 v0.10 发布内容 项目背景 平台功能 技术架构 v0.10 快速体验 启动项目 Trace 数据的发送 配置 Jaeger 数据源 Trace 数据的查询 项目简介 Mo ...
- 问题--如何快速复制Typora(Markdown)中的代码块/区块等
使用快捷键ctrl+E,选中相同格式的文字,然后快速复制即可
- phpcms - 获取单网页 , 例如关于我们
{pc:get sql="select * from phpcms_page where catid=2" num="1"} {loop ...
- Linux-用户管理-useradd-userdel-id
- [转帖]clickhouse安装部署以及版本选取
https://www.cnblogs.com/MrYang-11-GetKnow/p/15818768.html 1. 系统要求 ClickHouse 可以在任何具有 x86_64.AArch64 ...
- [转帖]Kafka Dashboard
https://grafana.com/grafana/dashboards/18276-kafka-dashboard/ Kafka resource usage and consumer lag ...
- [转帖]华为FusionSphere虚拟化解决方案介绍
https://huaweicloud.csdn.net/63566589d3efff3090b5d243.html?spm=1001.2101.3001.6650.2&utm_medium= ...