Java 理解 ThreadLocal

摘要:
ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态。进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤。
一. 对 ThreadLocal 的理解
1). ThreadLocal 概述
ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制,可以为每一个使用该变量的线程都提供一个变量值的副本,并且每一个线程都可以独立地改变自己的副本,而不会与其它线程的副本发生冲突。通过 ThreadLocal 存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种 隔离机制 。
2). ThreadLocal 在 JDK 中的定义
ThreadLocal
This class provides thread-local variables. These variables differ from their normal counterparts(副本) in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable (见下图) as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
我们可以从中摘出三条要点:
每个线程都有关于该 ThreadLocal变量 的私有值
每个线程都有一个独立于其他线程的上下文来保存这个变量的值,并且对其他线程是不可见的。
独立于变量的初始值
ThreadLocal 可以给定一个初始值,这样每个线程就会获得这个初始化值的一个拷贝,并且每个线程对这个值的修改对其他线程是不可见的。
状态与某一个线程相关联
ThreadLocal 不是用于解决共享变量的问题的,也不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用 ThreadLocal 至关重要。
3). 应用场景
类 ThreadLocal 主要解决的就是为每个线程绑定自己的值,以方便其处理自己的状态。形象地讲,可以将 ThreadLocal变量 比喻*局存放数据的盒子,盒子中可以存储每个线程的私有数据。例如,以下类用于生成对每个线程唯一的局部标识符。线程 ID 是在第一次调用 uniqueNum.get() 时分配的,在后续调用中不会更改。
import java.util.concurrent.atomic.AtomicInteger;
public class UniqueThreadIdGenerator {
private static final AtomicInteger uniqueId = new AtomicInteger(0);
private static final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return uniqueId.getAndIncrement();
}
};
public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
String name = "Thread-" + i;
threads[i] = new Thread(name){
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": "
+ uniqueNum.get());
}
};
threads[i].start();
}
System.out.println(Thread.currentThread().getName() + ": "
+ uniqueNum.get());
}
}/* Output(输出结果不唯一):
Thread-1: 2
Thread-0: 0
Thread-2: 3
main: 1
Thread-3: 4
Thread-4: 5
*///:~
二. 深入分析ThreadLocal类
下面,我们来看一下 ThreadLocal 的具体实现,该类一共提供的四个方法:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
其中,get()方法是用来获取 ThreadLocal变量 在当前线程中保存的值,set() 用来设置 ThreadLocal变量 在当前线程中的值,remove() 用来移除当前线程中相关 ThreadLocal变量,initialValue() 是一个 protected 方法,一般需要重写。
1、 原理探究
1). 切入点:get()
首先,我们先看其源码:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程对象
ThreadLocalMap map = getMap(t); // 获取当前线程的成员变量 threadLocals
if (map != null) {
// 从当前线程的 ThreadLocalMap 获取该 thread-local variable 对应的 entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value; // 取得目标值
}
return setInitialValue();
}
2).关键点:setInitialValue()
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue(); // 默认实现返回 null
Thread t = Thread.currentThread(); // 获得当前线程
ThreadLocalMap map = getMap(t); // 得到当前线程 ThreadLocalMap类型域 threadLocals
if (map != null)
map.set(this, value); // 该 map 的键是当前 ThreadLocal 对象
else
createMap(t, value);
return value;
}
我们紧接着看上述方法涉及到的三个方法:initialValue(),set(this, value) 和 createMap(t, value)。
(1) initialValue()
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the <tt>initialValue</tt> method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
* <p>This implementation simply returns <tt>null</tt>; if the
* programmer desires thread-local variables to have an initial
* value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null; // 默认实现返回 null
}
(2) createMap()
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); // this 指代当前 ThreadLocal 变量,为 map 的键
}
至此,可能大部分朋友已经明白了 ThreadLocal类 是如何为每个线程创建变量的副本的:
① 在每个线程Thread内部有一个 ThreadLocal.ThreadLocalMap 类型的成员变量 threadLocals,这个threadLocals就是用来存储实际的ThreadLocal变量副本的,键值为当前ThreadLocal变量,value为变量的副本(值);
② 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的值为value,存到 threadLocals;
③ 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在对应线程的threadLocals里面查找。
2、实例验证
下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println("父线程 main :");
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
test.set();
System.out.println("\n子线程 Thread-0 :");
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
}
}/* Output:
父线程 main :
1
main
子线程 Thread-0 :
12
Thread-0
*///:~
从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样,并且进一步得出:
三. ThreadLocal的应用场景
在 Java 中,类 ThreadLocal 解决的是变量在不同线程间的隔离性。 最常见的 ThreadLocal 使用场景有 数据库连接问题、Session管理等。
(1) 数据库连接问题
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
(2) Session管理
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
四. ThreadLocal 一般使用步骤
ThreadLocal 使用步骤一般分为三步:
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# Java
# 并发编程之ThreadLocal
# ThreadLocal
# ThreadLocal详解
# Java并发编程学习之ThreadLocal源码详析
# Java ThreadLocal原理解析以及应用场景分析案例详解
# JDK源码白话解读之ThreadLocal篇
# Java源码解析ThreadLocal及使用场景
# java编程ThreadLocal上下传递源码解析
# 自己的
# 的是
# 绑定
# 都有
# 键值
# 重写
# 会报
# 提供一个
# 在每个
# 是一个
# 就会
# 也不
# 是在
# 都不
# 就像
# 就能
# 多个
# 不需要
# 这段
# 我们可以
相关文章:
临沂网站制作公司有哪些,临沂第四中学官网?
学生网站制作软件,一个12岁的学生写小说,应该去什么样的网站?
建站之星安装提示数据库无法连接如何解决?
平台云上自助建站如何快速打造专业网站?
小程序网站制作需要准备什么资料,如何制作小程序?
php8.4新语法match怎么用_php8.4match表达式替代switch【方法】
网站制作费用多少钱,一个网站的运营,需要哪些费用?
免费视频制作网站,更新又快又好的免费电影网站?
手机网站制作与建设方案,手机网站如何建设?
测试制作网站有哪些,测试性取向的权威测试或者网站?
微信推文制作网站有哪些,怎么做微信推文,急?
历史网站制作软件,华为如何找回被删除的网站?
代购小票制作网站有哪些,购物小票的简要说明?
如何选择高性价比服务器搭建个人网站?
阿里云高弹*务器配置方案|支持分布式架构与多节点部署
如何通过虚拟主机快速搭建个人网站?
公司门户网站制作流程,华为官网怎么做?
网站制作大概多少钱一个,做一个平台网站大概多少钱?
制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?
定制建站哪家更专业可靠?推荐榜单揭晓
网站制作新手教程,新手建设一个网站需要注意些什么?
宝塔建站后网页无法访问如何解决?
建站主机选购指南:核心配置与性价比推荐解析
淘宝制作网站有哪些,淘宝网官网主页?
网站制作培训多少钱一个月,网站优化seo培训课程有哪些?
建站之星CMS建站配置指南:模板选择与SEO优化技巧
建站主机解析:虚拟主机配置与服务器选择指南
MySQL查询结果复制到新表的方法(更新、插入)
linux top下的 minerd 木马清除方法
定制建站平台哪家好?企业官网搭建与快速建站方案推荐
高性能网站服务器部署指南:稳定运行与安全配置优化方案
电视网站制作tvbox接口,云海电视怎样自定义添加电视源?
宿州网站制作公司兴策,安徽省低保查询网站?
深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?
网站建设制作需要多少钱费用,自己做一个网站要多少钱,模板一般多少钱?
建站之星展会模板:智能建站与自助搭建高效解决方案
如何用已有域名快速搭建网站?
简单实现Android文件上传
如何选择长沙网站建站模板?H5响应式与品牌定制哪个更优?
制作充值网站的软件,做人力招聘为什么要自己交端口钱?
实例解析Array和String方法
北京的网站制作公司有哪些,哪个视频网站最好?
如何在万网ECS上快速搭建专属网站?
建站之星各版本价格是多少?
如何通过WDCP绑定主域名及创建子域名站点?
高防服务器租用首荐平台,企业级优惠套餐快速部署
建站之星安装后如何自定义网站颜色与字体?
上海网站制作网页,上海本地的生活网站有哪些?最好包括生活的各个方面的?
c# 服务器GC和工作站GC的区别和设置
如何选择适配移动端的WAP自助建站平台?
*请认真填写需求信息,我们会在24小时内与您取得联系。