【Java并发系列03】ThreadLocal详解

img { border: solid 1px }

一、前言

  ThreadLocal这个对象就是为多线程而生的,没有了多线程ThreadLocal就没有存在的必要了。可以将任何你想在每个线程独享的对象放置其中,并在任何时候取出来。

二、基本用法

  ThreadLocal的使用方法其实特别简单:

  1. 在某个类(相当于工厂类,因为当获取不到的时候你要创建一个给它)中创建静态的ThreadLocal。
  2. 在别的地方调用它的set和get方法用来存放对象。

  下面展示一个样例:

package yiwangzhibujian;

public class ThreadLocalUse{
    //创建一个静态的ThreadLocal对象,当做仓库,存储每个线程自己的资源
    //此处用Object来代替,可以使连接Connection
    private static ThreadLocal<Object> store=new ThreadLocal<Object>();

    //当每个线程要获取一个线程资源的时候,调用get方法
    public Object get(){
        //首先去仓库中去寻找
        Object obj=store.get();
        //如果不存在那就创建一个给线程,并把创建的放到仓库中
        if(obj==null){
            obj=new Object();
            set(obj);
        }
        return obj;
    }

    //想将线程资源放到仓库调用set方法,
    public void set(Object obj){
        store.set(obj);
    }
}

  这是一个使用模板,可以根据具体情况来做相应改变。

三、ThreadLocal理解

3.1 ThreadLocal会导致内存泄漏吗

  首先明确表示使用ThreadLocal不会导致内存泄漏。现在做如下测试证明这个观点,有如下程序:

public class ThreadLocalTest{
    //创建一个ThreadLocal用来存储每个线程的对象
    private static ThreadLocal<Object> local=new ThreadLocal<Object>();
    //一个常量,用来创建10M大小的对象使用
    private static final int _1MB=1024*1024;

    public static void main(String[] args) throws Exception{
        //创建一个Runnable对象
        Runnable r=new Runnable(){
            @Override
            public void run(){
                System.out.println("子线程内创建对象并放置ThreadLocal中");
                //创建一个10M大小的数组,并将其放到ThreadLocal中
                byte[] data=new byte[10*_1MB];
                local.set(data);
                System.out.println("子线程运行结束,代表线程结束");
            }
        };
        //创建线程并运行
        Thread t=new Thread(r);
        t.start();

        System.out.println("主线程休眠一秒,为了让创建的子线程执行完毕");
        Thread.sleep(1000);
        System.out.println("休眠完成,主线程准备分配一个10M大小的数组");
        byte[] data2=new byte[10*_1MB];
        System.out.println("分配数组完毕");
    }
}

  在程序右键点击Run as->Run Configurations进行虚拟机配置:

  配置如下,-XX:+PrintGCDetails(打印垃圾回收日志) -XX:+PrintGCTimeStamps(打印回收时间,可不用) -Xms18M -Xmx18M(设置初始化和最大内存为18M,这么做的目的是,当创建两个10M大小的数组时,会内存溢出还是将ThreadLocal内的进行回收):

  首先介绍这个程序的目的就是创建一个对象放置到ThreadLocal中,看看垃圾回收器会不会在线程结束的时候对其进行回收。运行结果如下:

主线程休眠一秒,为了让创建的子线程执行完毕
子线程内创建对象并放置ThreadLocal中
子线程运行结束,代表线程结束
休眠完成,主线程准备分配一个10M大小的数组
1.102: [GC [PSYoungGen: 376K->224K(5376K)] 10616K->10464K(17664K), 0.0077785 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
1.110: [GC [PSYoungGen: 224K->224K(5376K)] 10464K->10464K(17664K), 0.0008971 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
1.111: [Full GC [PSYoungGen: 224K->0K(5376K)] [PSOldGen: 10240K->164K(12288K)] 10464K->164K(17664K) [PSPermGen: 3030K->3030K(21248K)], 0.0058052 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配数组完毕

  可以发现,当主线程准备分配一个10M大小的数组时发现内存不够,会调用垃圾处理器进行回收,通过[PSOldGen: 10240K->164K(12288K)] 可以看出,放入ThreadLocal中的对象被回收了。

3.2 使用全局Map替换ThreadLocal可不可以

  我曾经也想过使用全局Map来替换ThreadLocal,即使用如下代码表示展示的样例:

public class ThreadLocalUse{

    private static Map<Thread,Object> store=new HashMap<Thread,Object>();

    //当每个线程要获取一个线程资源的时候,调用get方法
    public Object get(){
        //首先去仓库中去寻找
        Object obj=store.get(Thread.currentThread());
        //如果不存在那就创建一个给线程,并把创建的放到仓库中
        if(obj==null){
            obj=new Object();
            set(obj);
        }
        return obj;
    }

    //想将线程资源放到仓库调用set方法,
    public void set(Object obj){
        store.put(Thread.currentThread(),obj);
    }
}

  这样也可以做到线程内共享对象,但是有一个致命的缺点,那就是当线程结束后,存储在Map中的对象必须手动清除,否则将会导致内存泄漏。

3.3 对比ThreadLocal和synchronized同步机制

  很多人都会对这两个对象进行比对,我也谈一下我自己的想法。

  使用synchronized是为了将多条语句进行原子化操作,比说对于递增操作i++,任意一个线程在执行代码时都要保证别的线程不能执行这个代码,否则就会产生脏数据,使用synchronized可以避免这一点。

  而使用ThreadLocal就是给每个线程存储对象用的。既然每个线程使用了自己的对象,没有了竞争就不会出现多线程相关的问题。

3.5 我对ThreadLocal的一点点理解

  本来呢,我也一直在想ThreadLocal设计的有一点点不合理,比如为什么只能存储一个对象呢,存储多个对象多好呀,或者Thread直接内置一个Map多好,就可以这样操作:

Thread.currentThread().map.put(key,value);
Thread.currentThread().map.get(key);

  我觉的我的想法很不错,但是至于他们为什么这个设计或许有别的意思。

  这一篇讲解了ThreadLocal,虽然和它和并发没有多大关系,但是它毕竟因多线程而生,所以还是涉及到它。

  未经许可禁止转载。

时间: 2024-08-19 01:11:01

【Java并发系列03】ThreadLocal详解的相关文章

Java 并发:Lock 框架详解

摘要: 我们已经知道,synchronized 是java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等.Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题.本文以synchronized与Lock的对比为切入点,对Java中的Lock框架的枝干部分进行了详细介绍,最后给出了锁的一些相关概念. 一. synchronized 的局限性

Java网络编程和NIO详解开篇:Java网络编程基础

Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为我们拥有网络.网络是一个神奇的东西,它改变了你和我的生活方式,改变了整个世界. 然而,网络的无标度和小世界特性使得它又是复杂的,无所不在,无所不能,以致于我们无法区分甚至无法描述. 对于一个码农而言,了解网络的基础知识可能还是从了解定义开始,认识OSI的七层协议模型,深入Socket内部,进而熟练地

Java网络编程和NIO详解6:Linux epoll实现原理详解

Java网络编程和NIO详解6:Linux epoll实现原理详解 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO https://blog.csdn.net/column/details/21963.html 部分代码会放在我的的Github:https://github.com/h2pl/ Linux epoll实现原理详解 在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者pol

Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introduction/ netty是基于NIO实现的异步事件驱动的网络编程框架,学完NIO以后,应该看看netty的实现,netty框架涉及的内容特别多,这里只介绍netty的基本使用和实现原理,更多扩展的内容将在以后推出. 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎

Java网络编程和NIO详解8:浅析mmap和Direct Buffer

Java网络编程与NIO详解8:浅析mmap和Direct Buffer 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO https://blog.csdn.net/column/details/21963.html 部分代码会放在我的的Github:https://github.com/h2pl/ Java网络编程与NIO详解8:浅析mmap和Direct Buffer 之前看到一篇文章说epoll中在维护epo

Java内存模型相关原则详解

在<Java内存模型(JMM)详解>一文中我们已经讲到了Java内存模型的基本结构以及相关操作和规则.而Java内存模型又是围绕着在并发过程中如何处理原子性.可见性以及有序性这三个特征来构建的.本篇文章就带大家了解一下相关概念.原则等内容. 原子性 原子性即一个操作或一系列是不可中断的.即使是在多个线程的情况下,操作一旦开始,就不会被其他线程干扰. 比如,对于一个静态变量int x两条线程同时对其赋值,线程A赋值为1,而线程B赋值为2,不管线程如何运行,最终x的值要么是1,要么是2,线程A和线

java虚拟机启动参数分类详解

官方文档见: http://docs.sun.com/source/819-0084/pt_tuningjava.html java启动参数共分为三类:其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容:其二是非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容:其三是非Stable参数(-XX),此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用: 一.标准参数中比较有用的: verbose -verbo

【java项目实战】Servlet详解以及Servlet编写登陆页面(二)

Servlet是Sun公司提供的一门用于开发动态web网页的技术.Sun公司在API中提供了一个servlet接口,我们如果想使用java程序开发一个动态的web网页,只需要实现servelet接口,并把类部署到web服务器上就可以运行了. 到底什么是Servlet呢? 通俗一点,只要是实现了servlet接口的java程序,均称Servlet.Servlet是由sun公司命名的,Servlet = Server + Applet(Applet表示小应用程序),Servlet是在服务器端运行的小

Java垃圾回收机制(GC)详解

Java垃圾回收机制(GC)详解 简介: 垃圾回收GC(Garbage Collection)是Java语言的核心技术之一,之前我们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机制并未改变.垃圾收集的目的在于清除不再使用的对象.GC通过确定对象是否被活动对象引用来确定是否收集该对象.GC首先要判断该对象是否是时候可以收集.两种常用的方法是引用计数和对象引用遍历. 垃圾收集的算法分析: Java语言规范没有明确地说明JVM使用哪种垃圾