基本介绍

  ThreadLocal很多地方叫线程本地变量,或者叫线程本地存储。ThreadLocal为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突,实现线程间的数据隔离,至于是如何实现的,下面会在实现原理中介绍。但是我们需要知道,threadLocal只是实现了变量在不同线程中的数据隔离,即保证了同一变量在不同的线程中传递时可以有不同的值,换句话说ThreadLocal构建的是在同一线程中的全局变量,并没有解决共享变量的问题,在多线程并发访问共享变量时依然会出现并发问题,当然更不存在解决了什么同步问题。

   但是这里有个ThreadLocal使用的误区,因为ThreadLocal能够是的参数在不同的方法中使用,所以有些小伙伴为了避免方法中写了过多的参数就把参数的使用这些参数放到了threadLocal中,其实这是不合理的设计,也不是ThreadLocal设计的初衷。

使用场景

  • 变量在每个线程需要有自己单独的实例
  • 实例需要在整个线程中共享,但不希望被多线程共享

实现原理

  要想了解ThreadLocal是如何为每个线程实现数据隔离的,就需要了解下ThreadLocal中几个重要的方法了。其实冲get和getMap方法中就可以明白了,每个线程中变量实例其实是放在一个类型为ThreadLocalMap的map的Value中的,而这个map是通过Thread中的变量threadLoacls来获得的,这样一来这个变量的值就会只存在于这个线程。另外我们注意到map中key是this,也就是ThreadLocal的实例,这也就是保证了不同的线程中使用的是同一个变量,但是可以有不同的值。

get()

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

getMap()

  1. ThreadLocalMap getMap(Thread t) {
  2. return t.threadLocals;
  3. }

set()

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

createMap()

  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }

initialValue()

  1. protected T initialValue() {
  2. return null;
  3. }

setInitialValue()

  1. private T setInitialValue() {
  2. T value = initialValue();
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null)
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. return value;
  10. }  

remove()

  1. public void remove() {
  2. ThreadLocalMap m = getMap(Thread.currentThread());
  3. if (m != null)
  4. m.remove(this);
  5. }

  

注意事项

  • ThreadLocal 并不解决线程间共享数据的问题
  • ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • ThreadLocalMap的map的Entry的key是对ThreadLocal的弱引用,主要是为了防止ThreadLocal回收失败造成内存溢出问题
  • ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
  • 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题
  • ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景
  • 使用ThreadLocal的get方法时必须先调用set,否者会报空指针,如果不像使用set方法,就必须重写initialValue方法
  • 使用 ThreadLocal 的时候,最好不要声明为静态的
  • 使用完 ThreadLocal ,最好手动调用 remove() 方法,例如上面说到的 Session 的例子,如果不在拦截器或过滤器中处理,不仅可能出现内存泄漏问题,而且会影响业务逻辑;

应用实例

  比如用来存储用户 Session。Session 的特性很适合 ThreadLocal ,因为 Session 之前当前会话周期内有效,会话结束便销毁。我们先笼统但不正确的分析一次 web 请求的过程:

  • 用户在浏览器中访问 web 页面;
  • 浏览器向服务器发起请求;
  • 服务器上的服务处理程序(例如tomcat)接收请求,并开启一个线程处理请求,期间会使用到 Session ;
  • 最后服务器将请求结果返回给客户端浏览器。

  从这个简单的访问过程我们看到正好这个 Session 是在处理一个用户会话过程中产生并使用的,如果单纯的理解一个用户的一次会话对应服务端一个独立的处理线程,那用 ThreadLocal 在存储 Session ,简直是再合适不过了。但是例如 tomcat 这类的服务器软件都是采用了线程池技术的,并不是严格意义上的一个会话对应一个线程。并不是说这种情况就不适合 ThreadLocal 了,而是要在每次请求进来时先清理掉之前的 Session ,一般可以用拦截器、过滤器来实现。

  除此之外,还有数据库链接,上下文管理器等都可以使用ThreadLocal。

session使用示例:

  1.  @Data
  2. public static class Session {
  3. private String id;
  4. private String user;
  5. private String status;
  6. }
  1. public class SessionHandler {
  2.  
  3. public static ThreadLocal<Session> session = new ThreadLocal<Session>();
  4.  
  5. public void createSession() {
  6. session.set(new Session());
  7. }
  8.  
  9. public String getUser() {
  10. return session.get().getUser();
  11. }
  12.  
  13. public String getStatus() {
  14. return session.get().getStatus();
  15. }
  16.  
  17. public void setStatus(String status) {
  18. session.get().setStatus(status);
  19. }
  20.  
  21. public static void main(String[] args) {
  22. new Thread(() -> {
  23. SessionHandler handler = new SessionHandler();
  24. handler.getStatus();
  25. handler.getUser();
  26. handler.setStatus("close");
  27. handler.getStatus();
  28. }).start();
  29. }
  30. }

带来的问题

资源消耗

  由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

内存溢出

  实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap 中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

  ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

java之threadlocal的使用的更多相关文章

  1. Java中ThreadLocal的设计与使用

    早在Java 1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择.使用这个工具类可以很简洁地编写出优美的多线程程 ...

  2. java的ThreadLocal类的使用方法

    java的ThreadLocal类的使用方法,ThreadLocal是一个支持泛型的类,用在多线程中用于防止并发冲突问题. 比如以下的一个样例,就是用于线程添加1,可是相互不冲突 package co ...

  3. [Java多线程]-ThreadLocal源码及原理的深入分析

    ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. //-------------------------- ...

  4. java中ThreadLocal类的使用

    ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,由于Key值不可重复, ...

  5. 深入研究java.lang.ThreadLocal类 (转)

    深入研究java.lang.ThreadLocal类     一.概述   ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thr ...

  6. Java多线程——ThreadLocal类的原理和使用

    Java多线程——ThreadLocal类的原理和使用 摘要:本文主要学习了ThreadLocal类的原理和使用. 概述 是什么 ThreadLocal可以用来维护一个变量,提供了一个ThreadLo ...

  7. java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用?

    java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用? 说明类java.lang.ThreadLocal的作用和原理.列举在哪些程序中见过Threa ...

  8. java中ThreadLocal的使用

    文章目录 在Map中存储用户数据 在ThreadLocal中存储用户数据 java中ThreadLocal的使用 ThreadLocal主要用来为当前线程存储数据,这个数据只有当前线程可以访问. 在定 ...

  9. java中threadlocal的理解

    [TOC] #java中threadlocal的理解##一.threadlocal的生命周期和ThreadLocalMap的生命周期可以吧TreadLocal看做是一个map来使用,只不过这个map是 ...

  10. Java:ThreadLocal小记

    Java:ThreadLocal小记 说明:这是看了 bilibili 上 黑马程序员 的课程 java基础教程由浅入深全面解析threadlocal 后做的笔记 内容 ThreadLocal 介绍 ...

随机推荐

  1. Python之Simple FTP (一)

    一.引言: 好久之前想写一个ftpserver的小daemon,但是一直拖着就没有写,这回正好处于放假的时候可以有时间来写写. 二.FTP需求功能: 1.用户认证系统 2.文件上传和下载功能 a.支持 ...

  2. Linux下通过server-status监控性能

    Linux下通过server-status监控性能 前提:安装好Apache,在opt/路径下 查看Apache的工作模式 可以知道是 prefork.c模式 配置server-status 性能 进 ...

  3. PEP 492 -- Coroutines with async and await syntax 翻译

    因为工作中慢慢开始用python的协程,所以想更好的理解一下实现方式,故翻译此文 原文中把词汇表放到最后,但是我个人觉得放在最开始比较好,这样可以增加当你看原文时的理解程度 词汇表 原生协程函数 Na ...

  4. TensorFlow 图片resize方法

    参见这篇博客 tensorflow里面用于改变图像大小的函数是tf.image.resize_images(image, (w, h), method):image表示需要改变此存的图像,第二个参数改 ...

  5. VM虚拟机下安装CentOS_6.5_x64

    在开始Linux的学习之前,我们必须要有一个实验环境.这里我选择了安装CentOS-6.5-x86_64的Minimal版本,大家也可以选择安装其他的Linux发行版.选择CentOS主要还是因为它比 ...

  6. 大批量delete 优化方案

    超过100万以上数据 删除的时候 会非常慢且产生大量日志文件 最大的问题是内存爆表 导致得多次重启服务才能删除整个库 暂时提出初步优化方案 1.设置日志为简单模式,处理完后恢复 ALTER DATAB ...

  7. Github + Hexo 搭建个人博客

    参考博客: 我是如何利用Github Pages搭建起我的博客,细数一路的坑 使用Hexo+Github一步步搭建属于自己的博客(基础) Hexo 准备工作: 本机已安装好 git, npm 和 no ...

  8. 6 个开源的家庭自己主动化工具 | Linux 中国

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/F8qG7f9YD02Pe/article/details/79466841 wx_fmt=jpeg& ...

  9. HBuilder打包React单页面,Android返回功能

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  10. node-sass 安装失败 Failed at the node-sass@4.9.2 postinstall script的解决

    控制台运行npm install时报错,报错信息如下: npm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! node-sass@4.9.2 postins ...