您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~

为了提高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的更多相关文章

  1. Java多线程之 ThreadLocal

    一.什么是ThreadLocal? 顾名思义它是local variable(线程局部变量).它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副 ...

  2. Java多线程基础-ThreadLocal

    感谢原文作者:Yuicon 原文链接:https://segmentfault.com/a/1190000016705955 序 在多线程环境下,访问非线程安全的变量时必须进行线程同步,例如使用 sy ...

  3. java 多线程(threadlocal)

    package com.example; import java.util.Random; public class App { public static class MyRunnable1 imp ...

  4. java多线程学习-ThreadLocal

    为了凑字,把oracle文档里介绍ThreadLocal抄过来 public class ThreadLocal<T> extends Object This class provides ...

  5. Java多线程:ThreadLocal

    一.ThreadLocal基础知识 ThreadLocal是线程的一个本地化对象,或者说是局部变量.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的 ...

  6. java 多线程 day06 threadLocal

    import java.util.HashMap;import java.util.Map;import java.util.Random; /** * Created by chengtao on ...

  7. java 多线程 :ThreadLocal 共享变量多线程不同值方案;InheritableThreadLocal变量子线程中自定义值,孙线程可继承

      ThreadLocal类的使用 变量值的共享可以使用public static变量的形式,所有的线程都是用同一个public static变量.如果想实现每一个线程都有自己的值.该变量可通过Thr ...

  8. java多线程--------深入分析 ThreadLocal 内存泄漏问题

    前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用ThreadLocal,就可能 ...

  9. java 多线程 一个博客

    http://blog.csdn.net/a352193394/article/category/2563875 Java多线程之~~~线程安全容器的非阻塞容器 在并发编程中,会经常遇到使用容器.但是 ...

  10. Java多线程系列——从菜鸟到入门

    持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...

随机推荐

  1. CM311-1a(S905L3系列)玩转桌面

    那安装了ambian后,玩转桌面(安装GUI桌面环境)有没有可能呢?那肯定啊!那桌面有什么用?当然有用,多一种玩法,可以写写代码,上网冲浪,学习linux语法什么的.而且单主机只要40左右,想想看可以 ...

  2. ORA-01950: no privileges on tablespace 'USERS'-- 解决办法

    ORA-01950: no privileges on tablespace 'USERS'   原因: 在表空间 "USERS" 无权限 解决办法:   用户登录,查看当前用户所 ...

  3. 【MIDO】乐理基础 与 python - 从零开始到编写柱式和弦与分解和弦

    本篇文章从律学开始,从十二平均律出发,介绍一些基础必要的乐理知识,然后编写python文件,输出和弦音频文件. 乐理知识部分: 一.律学简述(temperament)   1.概论   律学,又称&q ...

  4. 这三大特性,让 G1 取代了 CMS!

    大家好,我是树哥. 之前我们聊过 CMS 回收器,但那时候我们说 CMS 回收器已经落伍了,现在应该是用 G1 回收器的时候了.那么 G1 回收器到底有什么魔力,它比 CMS 回收器相比强在哪里呢?今 ...

  5. hadoop 不在 sudoers 文件中,此事将被报告。

    问题来源: 使用sudo命令,让hadoop用户使用root身份执行命令时报错: [hadoop@mydocker ~]$ sudo date [sudo] password for hadoop: ...

  6. c++基础思维导图2

    c++基础思维导图2 结构体 结构体的基本概念:用户自定义的数据类型 结构体定义和使用 struct 结构体名{结构体成员} struct 结构体名 变量名: struct 结构体名 变量名 = {成 ...

  7. Python入门系列(六)一篇学会python函数

    函数 函数是只在调用时运行的代码块. def my_function(): print("Hello from a function") my_function() 信息可以作为参 ...

  8. SpringMVC 03: 请求和响应的乱码解决 + SpringMVC响应Ajax请求

    请求或响应的中文乱码问题 tomcat9解决了get请求和响应的中文乱码问题,但是没有解决post请求或响应的中文乱码问题 tomcat10解决了get和post请求以及响应的中文乱码问题 考虑到实际 ...

  9. 第九十六篇:恶补JS基础

    好家伙,来补基础啦,补JS的基础 先来一些概念性的东西 1.什么是JavaScript?  javaScript的简写形式就是JS,一种广泛用于客户端Web开发的脚本语言,常用来给HTML网页添加动态 ...

  10. .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 (Timer 优化版)

    在上个月写过一篇 .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 的文章,当时 CronSchedule 的实现是使用了,每个服务都独立进入到一个 while 循环中,进行定期扫描是否 ...