java之threadlocal的使用
基本介绍
ThreadLocal很多地方叫线程本地变量,或者叫线程本地存储。ThreadLocal为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突,实现线程间的数据隔离,至于是如何实现的,下面会在实现原理中介绍。但是我们需要知道,threadLocal只是实现了变量在不同线程中的数据隔离,即保证了同一变量在不同的线程中传递时可以有不同的值,换句话说ThreadLocal构建的是在同一线程中的全局变量,并没有解决共享变量的问题,在多线程并发访问共享变量时依然会出现并发问题,当然更不存在解决了什么同步问题。
但是这里有个ThreadLocal使用的误区,因为ThreadLocal能够是的参数在不同的方法中使用,所以有些小伙伴为了避免方法中写了过多的参数就把参数的使用这些参数放到了threadLocal中,其实这是不合理的设计,也不是ThreadLocal设计的初衷。
使用场景
- 变量在每个线程需要有自己单独的实例
- 实例需要在整个线程中共享,但不希望被多线程共享
实现原理
要想了解ThreadLocal是如何为每个线程实现数据隔离的,就需要了解下ThreadLocal中几个重要的方法了。其实冲get和getMap方法中就可以明白了,每个线程中变量实例其实是放在一个类型为ThreadLocalMap的map的Value中的,而这个map是通过Thread中的变量threadLoacls来获得的,这样一来这个变量的值就会只存在于这个线程。另外我们注意到map中key是this,也就是ThreadLocal的实例,这也就是保证了不同的线程中使用的是同一个变量,但是可以有不同的值。
get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
getMap()
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
createMap()
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
initialValue()
protected T initialValue() {
return null;
}
setInitialValue()
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
注意事项
- 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使用示例:
@Data
public static class Session {
private String id;
private String user;
private String status;
}
public class SessionHandler {
public static ThreadLocal<Session> session = new ThreadLocal<Session>();
public void createSession() {
session.set(new Session());
}
public String getUser() {
return session.get().getUser();
}
public String getStatus() {
return session.get().getStatus();
}
public void setStatus(String status) {
session.get().setStatus(status);
}
public static void main(String[] args) {
new Thread(() -> {
SessionHandler handler = new SessionHandler();
handler.getStatus();
handler.getUser();
handler.setStatus("close");
handler.getStatus();
}).start();
}
}
带来的问题
资源消耗
由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用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的使用的更多相关文章
- Java中ThreadLocal的设计与使用
早在Java 1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择.使用这个工具类可以很简洁地编写出优美的多线程程 ...
- java的ThreadLocal类的使用方法
java的ThreadLocal类的使用方法,ThreadLocal是一个支持泛型的类,用在多线程中用于防止并发冲突问题. 比如以下的一个样例,就是用于线程添加1,可是相互不冲突 package co ...
- [Java多线程]-ThreadLocal源码及原理的深入分析
ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. //-------------------------- ...
- java中ThreadLocal类的使用
ThreadLocal是解决线程安全问题一个很好的思路,ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,由于Key值不可重复, ...
- 深入研究java.lang.ThreadLocal类 (转)
深入研究java.lang.ThreadLocal类 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thr ...
- Java多线程——ThreadLocal类的原理和使用
Java多线程——ThreadLocal类的原理和使用 摘要:本文主要学习了ThreadLocal类的原理和使用. 概述 是什么 ThreadLocal可以用来维护一个变量,提供了一个ThreadLo ...
- java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用?
java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用? 说明类java.lang.ThreadLocal的作用和原理.列举在哪些程序中见过Threa ...
- java中ThreadLocal的使用
文章目录 在Map中存储用户数据 在ThreadLocal中存储用户数据 java中ThreadLocal的使用 ThreadLocal主要用来为当前线程存储数据,这个数据只有当前线程可以访问. 在定 ...
- java中threadlocal的理解
[TOC] #java中threadlocal的理解##一.threadlocal的生命周期和ThreadLocalMap的生命周期可以吧TreadLocal看做是一个map来使用,只不过这个map是 ...
- Java:ThreadLocal小记
Java:ThreadLocal小记 说明:这是看了 bilibili 上 黑马程序员 的课程 java基础教程由浅入深全面解析threadlocal 后做的笔记 内容 ThreadLocal 介绍 ...
随机推荐
- C# WebAPI学习
WebApi是微软在VS2012 MVC4版本中绑定发行的,WebApi是完全基于Restful标准的框架.RestFul: (英文:Representational State Transfer,简 ...
- 伟哥对RTO & RPO的通俗理解
RTO (Recovery Time Objective,复原时间目标)是企业可容许服务中断的时间长度.比如说灾难发生后半天内便需要恢复,RTO值就是十二小时: RPO (Recovery Point ...
- Django模版基本标签详解
一.if/else{% if %} 标签检查(evaluate)一个变量,如果这个变量为真(即,变量存在,非空,不是布尔值假),系统会显示在 {% if %} 和 {% endif %} 之间的任何内 ...
- spring mvc 传入中文参数乱码问题解决
一个简单的学习springmvc的demo中,当http请求传入中文参数时,在controller中接受到的参数就已经是乱码了,经百度一番解决方案如下: 1. get请求方式乱码解决 对于get方式, ...
- CentOS5.9 编译Emacs 24
从Emacs官方网站下载最新版解压后,执行 ./configure 得到错误信息: configure: error: The following required libraries were no ...
- 【OCR技术系列之六】文本检测CTPN的代码实现
这几天一直在用Pytorch来复现文本检测领域的CTPN论文,本文章将从数据处理.训练标签生成.神经网络搭建.损失函数设计.训练主过程编写等这几个方面来一步一步复现CTPN.CTPN算法理论可以参考这 ...
- XML格式化工具
做接口开发的时候,往往接受参数或返回值是一个XML的字符串.如下图,不方便辨识 两种方法, 1.将它保存为xxx.xml,然后用浏览器打开.这种方法稍微有些麻烦. 2.使用 UltraEdit 工具
- mysql分区方案的研究
笔者觉得,分库分表确实好的.但是,动不动搞分库分表,太麻烦了.分库分表虽然是提高数据库性能的常规办法,但是太麻烦了.所以,尝试研究mysql的分区到底如何. 之前写过一篇文章,http://www.c ...
- 解决MAMP启动mysql服务 但是Navicat连接不上
问题重现 错误信息 2003 - Can't connect to MySQL server on '127.0.0.1' (61 "Connection refused") 解决 ...
- mui 浏览器一样自动缩放
<!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...