ThreadLocal基础部分

ThreadLoal的作用

保存线程的独立变量,即每个线程维护一份。这种变量在线程的生命周期内起作用,减少同一个线程内多个函数之间公共变量传递麻烦。

使用场景

需要给不同的线程保存不同的信息时。

基础使用

public class TestThreadLocal {

    private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>();
// private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
//
// @Override
// protected Integer initialValue() {
// return 0;
// }
// }; public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(3);
System.out.println("t2:"+threadLocal.get());
}
}); t1.start();
t2.start();
System.out.println(threadLocal.get()); }
}

如果需要设置默认值的话,可以实现initialValue方法。

典型场景1:我们知道SimpleDateFormat的对象如果多线程使用的话会有线程不安全的问题。具体代码如下:

public class TestThreadLocal {

    public static ExecutorService executorService = Executors.newFixedThreadPool(16);

    private static SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws InterruptedException {

       for (int i=0;i<1000;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
String format = simpleDateFormat.format(new Date());
try {
Date parse = simpleDateFormat.parse("2021-09-01 00:00:00");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(format);
}
}); }
Thread.sleep(3000);
executorService.shutdownNow(); }
}

运行结果如下:

可以看出,发生了异常。

方法1:我们可以改为每次都new一个新的SimpleDateFormat对象的话,这样再运行是没问题的。但是有些资源浪费。

方法2:使用ThreadLocal来解决。假设线程池里共16个线程,那我们总共16个SimpleDateFormat对象就可以应付所有的日期格式化的调用。

代码如下:

public class TestThreadLocal {

    public static ExecutorService executorService = Executors.newFixedThreadPool(16);

    private static ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<SimpleDateFormat>(){

        @Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}; private static SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) throws InterruptedException { for (int i=0;i<1000;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
String format = threadLocal.get().format(new Date());
try {
Date parse = threadLocal.get().parse("2021-09-01 00:00:00");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(format);
}
}); }
Thread.sleep(3000);
executorService.shutdownNow(); }
}

注意: 如果不使用线程池,线程结束,线程里的threadLocalMap也会被回收。但是如果使用线程池,线程池里面的线程会被复用,线程里的threadLocalMap不会被回收,就造成了内存泄漏。按照正确的使用方法应该是每次用完了remove,但是这样效率就很低。还不如方法1每次去new一个新的SimpleDateFormat对象。(但个人觉得其实还好,泄漏一点也没关系,不过threadlocal毕竟不是专门解决线程安全问题的,不推荐这么用)

正确使用方法

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据
  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

ThreadLocal 高级部分

ThreadLocal为什么会内存泄露?

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

试想:

一个线程对应一块工作内存,线程可以存储多个ThreadLocal。那么假设,开启1万个线程,每个线程创建1万个ThreadLocal,也就是每个线程维护1万个ThreadLocal小内存空间,而且当线程执行结束以后,假设这些ThreadLocal里的Entry还不会被回收,那么将很容易导致堆内存溢出。

怎么办?难道JVM就没有提供什么解决方案吗?

答案:

  1. JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
  2. JVM利用调用remove、get、set方法的时候,顺道回收脏value值。

ThreadLocal的关系图如下所示:

Thread里面维护了一个ThreadLocalMap,这个map里面的key是弱引用的readLocal实例。value是我们设置进去的值。当把treadLocal实例对象置为null后,没有任何强引用指向threadLocal实例,所以theadLocal将会被gc回收。但是我们的value不会被回收,因为存在一个thread连接过来的强引用。只有当thread结束后,强引用断开,map、value等将全部被回收。

如下图:

但是很多时候我们使用线程池,为了复用线程,thread生命周期没有结束,所以无法回收,造成内存泄漏。

ThreadLocal基本使用和内存泄漏分析的更多相关文章

  1. Java内存泄漏分析与解决方案

    Java内存泄漏是每个Java程序员都会遇到的问题,程序在本地运行一切正常,可是布署到远端就会出现内存无限制的增长,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统崩盘,作者用自已的亲身经历 ...

  2. Android内存泄漏分析及调试

    尊重原创作者,转载请注明出处: http://blog.csdn.net/gemmem/article/details/13017999 此文承接我的另一篇文章:Android进程的内存管理分析 首先 ...

  3. Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  4. Java内存泄漏分析系列之五:常见的Thread Dump日志案例分析

    原文地址:http://www.javatang.com 症状及解决方案 下面列出几种常见的症状即对应的解决方案: CPU占用率很高,响应很慢 按照<Java内存泄漏分析系列之一:使用jstac ...

  5. Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析

    原文地址:http://www.javatang.com 一个典型的thread dump文件主要由一下几个部分组成: 上图将JVM上的线程堆栈信息和线程信息做了详细的拆解. 第一部分:Full th ...

  6. Javascript的内存泄漏分析

    作为程序员(更高大尚的称谓:研软件研发)的我们,无论是用Javascript,还是.net, java语言,肯定都遇到过内存泄漏的问题.只不过他们都有GC机制来帮助程序员完成内存回收的事情,如果你是C ...

  7. Android内存泄漏分析实战

    内存泄漏简单介绍 java能够保证当没有引用指向对象的时候,对象会被垃圾回收器回收.与c语言自己申请的内存自己释放相比,java程序猿轻松了非常多.可是并不代表java程序猿不用操心内存泄漏.当jav ...

  8. (转)Android内存泄漏分析及调试

      http://blog.csdn.net/gemmem/article/details/13017999 此文承接我的另一篇文章:Android进程的内存管理分析  首先了解一下dalvik的Ga ...

  9. 使用Eclipse Memory Analyzer进行内存泄漏分析三部曲

    源地址:http://seanhe.iteye.com/blog/898277 一.准备工作  分析较大的dump文件(根据我自己的经验2G以上的dump文件就需要使用以下介绍的方法,不然mat会出现 ...

随机推荐

  1. wpf 获得exe的运行路径

    System.Reflection.Assembly.GetEntryAssembly().Location

  2. The Programmer's Oath程序员的誓言----鲍勃·马丁大叔(Bob Martin)

    In order to defend and preserve the honor of the profession of computer programmers, I Promise that, ...

  3. "排序二叉树"之探幽

    /*怎么理解排序二叉树呢?在二叉树的基本定义上增加两个基本条件: (1)所有左子树的节点数值都小于此节点的数值: (2)所有右节点的数值都大于此节点的数值. */ 1 /*************** ...

  4. Spring第一课:基于XML装配bean(四),三种实例化方式:默认构造、静态工厂、实例工厂

    Spring中基于XML中的装配bean有三种方式: 1.默认构造 2.静态工厂 3.实例工厂 1.默认构造 在我们在Spring的xml文件中直接通过:     <bean id=" ...

  5. 【SpringMVC】域对象共享数据

    使用ModelAndView向request域对象共享数据 index.html <a th:href="@{/testModelAndView}">使用ModelAn ...

  6. el-upload上传文件和表单一起提交+后端接收代码

    目录 一.前言 二.前端页面展示 三.表单代码 四.Data部分 五.JS方法 六.后端接收方式 七.总结 一.前言 我们在做前端时,会遇到这样的需求,上传Excel文件,并且还要和填写的表单数据,一 ...

  7. Clean Architecture For RazorPage 实现多语言和本地化

    最近终于把多语言功能加上了,这次就再发一篇,讲一下在asp.net core环境下如何实现多语言和本地化(Globalization and localization)功能,主要参看:ASP.NET ...

  8. HashSet的add()方法源码解析(jdk1.8)

    HashSet 实现了Set接口 实际上是HashMap 可以存null,但只能有一个 不保证元素是有序的,取决于hash后,在确定索引结果 add源码 //核心操作putVal final V pu ...

  9. Final Cut Pro X 10.4.7 修改长宽比, 截取某一段视频导出

    新建项目时设置过长宽比例,现在要修改 窗口 - 项目属性 - 修改 - 自定义 要注意的是,导出时也要选择一下比例 截取某一段视频导出 将鼠标移到你要截取的片段的开始处,点击快捷键 i(标记-设定范围 ...

  10. Shiro02

    Shiro认证 Pom依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>s ...