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 收集多 ...
随机推荐
- leaflet 绘制 带箭头的线
箭头不是画的线段,是贴的图标,再按方向旋转一下. 代码: //添加箭头线 function addLineDirection(polylinePointArr, source, target) { v ...
- 2021杭电多校第零场 & 2021湘潭全国邀请赛 补题记录
比赛链接:Here 本场题目重现于 2021湘潭全国邀请赛 A - A+B Problem (签到) 根据题意处理即可 int main() { cin.tie(nullptr)->sync_w ...
- 14、SpringBoot-easyexcel导出excle
系列导航 springBoot项目打jar包 1.springboot工程新建(单模块) 2.springboot创建多模块工程 3.springboot连接数据库 4.SpringBoot连接数据库 ...
- 【内核】深入分析内核panic(一)--内核问题的原因
1 概述 linux内核包括进程管理.内存管理.中断管理.设备驱动.同步机制等各种模块,它们共同运行在一个共享的地址空间中,因此在运行中一旦出现问题,彼此之间可能具有千丝万缕的联系. 而且与用户态不同 ...
- 基于python+django的酒店预定网站-酒店管理系统
该系统是基于python+django开发的酒店预定管理系统.适用场景:大学生.课程作业.毕业设计.学习过程中,如遇问题可在github给作者留言. 演示地址 前台地址: http://hotel.g ...
- 最近遇到的问题记录:UrlEncode、UrlDecode
本文阅读前了解知识:什么时候需要使用UrlEncode和UrlDecode函数 作者使用谷歌浏览器,通过按下F12对第三方网站http协议的接口抓包进行分析操作. 场景 运维小哥哥偶尔使用某某外包公司 ...
- Linux-目录-cd-mdkir-rm-ls-pwd
- [转帖]oracle导出千万级数据为csv格式
当数据量小时(20万行内),plsqldev.sqlplus的spool都能比较方便进行csv导出,但是当数据量到百万千万级,这两个方法非常慢而且可能中途客户端就崩溃,需要使用其他方法. 一. sql ...
- [转帖]如何理解 kernel.pid_max & kernel.threads-max & vm.max_map_count
https://www.cnblogs.com/apink/p/15728381.html 背景说明 运行环境信息,Kubernetes + docker .应用系统java程序 问题描述 首先从Ku ...
- [转帖]为什么需要在脚本文件的开头加上#!/ bin / bash?
本文翻译自:Why do you need to put #!/bin/bash at the beginning of a script file? I have made Bash scripts ...