浅谈ThreadLocal
1. ThreadLocal简介
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
2. ThreadLocal与Synchronized的区别
ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。
但是ThreadLocal与synchronized有本质的区别:
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本
,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
一句话理解ThreadLocal,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了。
3. 兩大使用场景
3.1 典型场景1:
每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有 SimpleDate Format和 Random)
public class ThreadLocalNormalUsage00 {private static ExecutorService threadPool = Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException {for (int j = 0; j < 1000; j++) {int i = j;threadPool.execute(() -> System.out.println(date(i)));Thread.sleep(100L);}threadPool.shutdown();}public static String date(int seconds) {return ThreadSafeFormatter.dateFormatThreadLocal.get().format(new Date(seconds * 1000L));}
}
class ThreadSafeFormatter {public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal= ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}
3.2 典型场景2:
每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦。例如用 Threadlocal保存一些业务内容(用户权限信息、从用户系统取到的用户名、 user id等),这些信息在同一个线程內相同,但是不同的线程使用的业务内容是不相同的。
package com.zeny.threadstudy.threadpool;public class ThreadLocalNormalUsage06 {public static void main(String[] args) {new Service1().process();}
}
class Service1 {public void process() {User user = new User("超哥");UserContextHolder.holder.set(user);new Service2().process();}
}
class Service2 {public void process() {User user = UserContextHolder.holder.get();System.out.println("Service2 get user message: " + user);new Service3().process();}
}
class Service3 {public void process() {User user = UserContextHolder.holder.get();System.out.println("Service3 get user message: " + user);UserContextHolder.holder.remove();}
}
class UserContextHolder {public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {String name;public User(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}
}
4.Threadlocal的两个作用
1.让某个需要用到的对象在线程间隔离(每个线程都有自己的独立的对象)
2.在任何方法中都可以轻松获取到该对象
5.ThreadLocal原理
5.1 ThreadLocal的set()方法
public void set(T value) {//1、获取当前线程Thread t = Thread.currentThread();//2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,//则直接更新要保存的变量值,否则创建threadLocalMap,并赋值ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);else// 初始化thradLocalMap 并赋值createMap(t, value);}
从上面的代码可以看出,ThreadLocal 在set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。那么ThreadLocalMap又是什么呢,还有createMap又是怎么做的,我们继续往下看。大家最后自己再idea上跟下源码,会有更深的认识。
static class ThreadLocalMap {
static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object). Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table. Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}}
可看出ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。
//这个是threadlocal 的内部方法
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}//ThreadLocalMap 构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}
5.2 ThreadLocal的get方法
public T get() {//1、获取当前线程Thread t = Thread.currentThread();//2、获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);//3、如果map数据为空,if (map != null) {//3.1、获取threalLocalMap中存储的值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为nullreturn setInitialValue();}private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}
5.3 ThreadLocal的remove()方法
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}
remove方法,直接将ThrealLocal 对应的值从当前相差Thread中的ThreadLocalMap中删除。为什么要删除,这涉及到内存泄露的问题。实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。ThreadLocal其实是与线程绑定的一个变量,如此就会出现一个问题:如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。举个例字,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会开始持续膨胀。
5.4 ThreadLocal与Thread,ThreadLocalMap之间的关系
Thread、THreadLocal、ThreadLocalMap之间啊的数据关系图
从这个图中我们可以非常直观的看出,ThreadLocalMap其实是Thread线程的一个属性值,而ThreadLocal是维护ThreadLocalMap
这个属性指的一个工具类。Thread线程可以拥有多个ThreadLocal维护的自己线程独享的共享变量(这个共享变量只是针对自己线程里面共享)
6.ThreadLocal出现空指针问题
如下代码,当执行get方法的时候,会获取null,然后进行拆箱操作,因为对象为空,所以报空指针异常。
package com.zeny.threadstudy.threadpool;public class Main {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public int getValue() {return threadLocal.get();}public void setValue(int value) {threadLocal.set(value);}public static void main(String[] args) {Main main = new Main();main.getValue(); //会出现空指针异常main.setValue(10);main.getValue();}
}
原文链接:https://blog.csdn.net/u010445301/article/details/111322569
浅谈ThreadLocal相关推荐
- 浅谈 ThreadLocal
有时,你希望将每个线程数据(如用户ID)与线程关联起来.尽管可以使用局部变量来完成此任务,但只能在本地变量存在时才这样做.也可以使用一个实例属性来保存这些数据,但是这样就必须处理线程同步问题.幸运的是 ...
- java 多线程同步_浅谈Java多线程(状态、同步等)
Java多线程是Java程序员必须掌握的基本的知识点,这块知识点比较复杂,知识点也比较多,今天我们一一来聊下Java多线程,系统的整理下这部分内容. 一.Java中线程创建的三种方式: 1.通过继承T ...
- mysql 多租户_数据层的多租户浅谈
数据层的多租户浅谈 刘 盛彬, 任 乐天, 和 陈 争云 2013 年 12 月 26 日发布 在上一篇"浅析多租户在 Java 平台和某些 PaaS 上的实现"中我们谈到了应用层 ...
- 浅谈在游戏陪玩开发中常见的几种加密算法及实现
前言 数字签名.信息加密是游戏陪玩开发前后端都经常需要使用到的技术,应用场景包括了用户登入.交易.信息通讯.oauth 等等,不同的应用场景需要游戏陪玩开发时使用到不同的签名加密算法,或者需要搭配不一 ...
- 浅谈MySQL存储引擎-InnoDBMyISAM
浅谈MySQL存储引擎-InnoDB&MyISAM 存储引擎在MySQL的逻辑架构中位于第三层,负责MySQL中的数据的存储和提取.MySQL存储引擎有很多,不同的存储引擎保存数据和索引的方式 ...
- 【大话设计模式】——浅谈设计模式基础
初学设计模式给我最大的感受是:人类真是伟大啊!单单是设计模式的基础课程就让我感受到了强烈的生活气息. 个人感觉<大话设计模式>这本书写的真好.让貌似非常晦涩难懂的设计模式变的生活化.趣味化 ...
- 学校计算机机房好处,浅谈学校计算机机房维护
浅谈学校计算机机房维护 现在的学校机房都配置了数量较多的计算机,而且机房的使用非常频繁.对于怎样维护好计算机,特别是计算机软件系统,对广大计算机教师来说是一个很重要且非常现实的问题.下面就本人在 ...
- java 中的单元测试_浅谈Java 中的单元测试
单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...
- mybatis与php,浅谈mybatis中的#和$的区别
浅谈mybatis中的#和$的区别 发布于 2016-07-30 11:14:47 | 236 次阅读 | 评论: 0 | 来源: 网友投递 MyBatis 基于Java的持久层框架MyBatis 本 ...
最新文章
- 2008.09.20三多寨
- 远控免杀专题8---BackDoor-Facktory免杀
- vector中find和find_if的用法
- Guns 添加功能实现_入门试炼05
- axios的用法详解
- Java学习笔记之:Java 继承
- python 存redis失败无提示_python如何关闭redis
- Android 性能测试——Memory Monitor 工具
- 挨踢部落故事汇(2):机缘所致转型之路
- gdc服务器维修员登录密码,gdc服务器密码
- 投影幕布尺寸计算器_投影距离和屏幕尺寸计算器Ver1.02.xls
- 第一章(1.2) 机器学习算法工程师技能树
- 服务器更换固态后如何安装系统,更换固态硬盘后安装操作系统的两种常用方法...
- linux网卡驱动realtek,Linux系统Realtek网卡驱动安装
- xp重启计算机的快捷键,xp电脑关机重启快捷键如何使用
- swoole基础教程-1.简介
- 思岚科技定位导航技术凸显 成为服务机器人企业首选品牌
- 学计算机专业还是数学专业课,数学专业的数学和计算机专业的数学的比较.doc...
- B. Fridge Lockers
- 读书笔记:《冯唐成事心法》