ThreadLocal源代码分析
最早接触ThreadLocal这个东东,还是在学Hibernate的时候,当时看ThreadLocal没明白是干什么的,后来在网上查才明白ThreadLocal的用途,ThreadLocal其实蛮有用的,总结一下具体的原理及用法。
虽然支持线程局部变量早就是许多线程工具,但
Java Threads API 的最初设计却没有这项有用的功能。而且,最初的实现也相当低效。ThreadLocal 极少受到关注,但对简化线程安全并发程序的开发来说,它却是很方便的。
ThreadLocal要解决的是什么问题呢?
一个本来应该线程安全的类,里面有一个线程不安全的变量,这样这个类也就线程不安全了,那应该怎么办呢?我们如果能够把这个变量和每个线程绑定,也就是每一个线程拥有这个变量的副本,那么整个对象就成为线程安全的了。一个解决方案就是使用一个Map,key对应于当前的线程,value对应于那个变量,这样我们就可以轻易的获取到当前线程的那个变量的副本了,ThreadLocal就是这个东东。
我们不妨写写大致的代码:
- public class ThreadLocal{
- private Map values = Collections.synchronizedMap(new HashMap());
- public Object get(){
- Thread curThread = Thread.currentThread();
- Object o = values.get(curThread);
- if (o == null && !values.containsKey(curThread)){
- o = initialValue();
- values.put(curThread, o);
- }
- return o;
- }
- public void set(Object newValue){
- values.put(Thread.currentThread(), newValue);
- }
- public Object initialValue(){
- return null;
- }
- }
当然java的ThreadLocal实现的总体思路也大致如此。
我们看看jdk提供的api文档:
T get()
返回此线程局部变量的当前线程副本中的值,如果变量没有用于当前线程的值,则先将其初始化为调用
initialValue() 方法返回的值
protected T
initialValue()
返回此线程局部变量的当前线程的“初始值”。
void remove()
移除此线程局部变量当前线程的值。
void set(T value)
将此线程局部变量的当前线程副本中的值设置为指定值。
我们看jdk文档提供了一个例子:
- 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 int getCurrentThreadId() {
- return uniqueId.get();//应该是return uniqueNum.get();
- }
- } // UniqueThreadIdGenerator
这个例子我看的时候没看懂,其实是有错误的 return uniqueId.get();,应该是return uniqueNum.get();我用的是中文翻译过来的jdk api 1.6.0,不知道
大家的jdk帮助文档有没有这个问题.
我们看看Hibernate官方文档提供的一个通过ThreadLocal维护Session的例子:
- public class HibernateUtil {
- private static final SessionFactory sessionFactory;
- static {
- try {
- sessionFactory = new Configuration().configure()
- .buildSessionFactory();
- } catch (Throwable ex) {
- ex.printStackTrace();
- throw new ExceptionInInitializerError(ex);
- }
- }
- public static final ThreadLocal tLocalsess = new ThreadLocal();
- // 取得session
- public static Session currentSession() {
- Session session = (Session) tLocalsess.get();
- // 打开一个新的session,如果当前的不可用.
- try {
- if (session == null || !session.isOpen()) {
- session = openSession();
- tLocalsess.set(session);
- }
- } catch (HibernateException e) {
- // 抛出HibernateException异常
- e.printStackTrace();
- }
- return session;
- }
- public static void closeSession() {
- Session session = (Session) tLocalsess.get();
- tLocalsess.set(null);
- try {
- if (session != null && session.isOpen()) {
- session.close();
- }
- } catch (HibernateException e) {
- //抛出 InfrastructureException异常
- }
- }
- //other code
- }
- 这段代码借助threadLocal,每一个线程保存一个session实例,从而避免线程内的频繁创建和销毁session.
其它适合使用 ThreadLocal 但用池却不能成为很好的替代技术的应用程序包括存储或累积每线程上下文信息以备稍后检索之用这样的应用程序。例如,假设您想创建一个用于管理多线程应用程序调试信息的工具。您可以用
DebugLogger 类作为线程局部容器来累积调试信息。在一个工作单元的开头,您清空容器,而当一个错误出现时,您查询该容器以检索这个工作单元迄今为止生成的所有调试信息。
用 ThreadLocal 管理每线程调试日志
- public class DebugLogger {
- private static class ThreadLocalList extends ThreadLocal {
- public Object initialValue() {
- return new ArrayList();
- }
- public List getList() {
- return (List) super.get();
- }
- }
- private ThreadLocalList list = new ThreadLocalList();
- private static String[] stringArray = new String[0];
- public void clear() {
- list.getList().clear();
- }
- public void put(String text) {
- list.getList().add(text);
- }
- public String[] get() {
- return list.getList().toArray(stringArray);
- }
- }
在您的代码中,您可以调用
DebugLogger.put() 来保存您的程序正在做什么的信息,而且,稍后如果有必要(例如发生了一个错误),您能够容易地检索与某个特定线程相关的调试信息。与简单地把所有信息转储到一个日志文件,然后努力找出哪个日志记录来自哪个线程(还要担心线程争用日志纪录对象)相比,这种技术简便得多,也有效得多。
ThreadLocal 在基于
servlet 的应用程序或工作单元是一个整体请求的任何多线程应用程序服务器中也是很有用的,因为在处理请求的整个过程中将要用到单个线程。您可以通过每线程单子技术用 ThreadLocal 变量来存储各种每请求(per-request)上下文信息。
ThreadLocal 能带来很多好处。它常常是把有状态类描绘成线程安全的,或者封装非线程安全类以使它们能够在多线程环境中安全地使用的最容易的方式。使用 ThreadLocal 使我们可以绕过为实现线程安全而对何时需要同步进行判断的复杂过程,而且因为它不需要任何同步,所以也改善了可伸缩性。除简单之外,用ThreadLocal 存储每线程单子或每线程上下文信息在归档方面还有一个颇有价值好处:通过使用 ThreadLocal ,存储在 ThreadLocal 中的对象都是不被线程共享的是清晰的,从而简化了判断一个类是否线程安全的工作。
当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。
下面是对ThreadLocal 类的源码分析下:
void set(Object value):设置当前线程的线程局部变量的值。
public Object get() :该方法返回当前线程所对应的线程局部变量。 (同一个线程获取同一个变量的引用)
public void remove() :将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue():返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的(在上面的例子也可以看到,其实就是一个默认值的设置)。这个方法是一个延迟调用方法,在线程第1次调用 get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
1、void set(Object value)方法分析
- public void set(T value) {
- Thread t = Thread.currentThread();
- //查看当前线程中的ThreadLocalMap 是否存在。
- ThreadLocalMap map = getMap(t);
- //存在的话就把当前线程设置进ThreadLocalMap中。
- if (map != null)
- map.set(this, value);
- else
- //否则生成一个ThreadLocalMap 对象,并把值赋到当前线程的ThreadLocalMap中
- createMap(t, value);
- }
- //-------------------------------解析的代码分割线-------------------------------
- //返回当前线程的ThreadLocalMap
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
- //创建新的ThreadLocalMap赋值到当前线程
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
下面说下关于ThreadLocalMap 类,这个类有点类似于WeakHashMap(这个类对于解决某些问题提供了很好的性能上的方案,有空再继续深入了解。),虽然ThreadLocalMap没有继承Map的,但它具有Map的一些特性,有关这些特性就不说了,主要说说它们的一个很大的共同点就是,它们的key 都是弱引用的对象,因此在该键没有被引用时,将会被JVM收集。(有关弱引用的解析,请参考我的博客上的:http://zhxing.iteye.com/admin/blogs/571007)
ThreadLocalMap是ThreadLocal的一个嵌套类,它的一些源码如下:
- 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;
- }
- }
2、 public Object get() 方法分析
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- //这个是传入当前的ThreadLocal 对象引用获取所对应的值
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null)
- return (T)e.value;
- }
- //如果map 对象为空,说明是第一次调用这个方法,之前也没有调用set()方法
- return setInitialValue();
- }
- //-------------------------------解析的代码分割线-------------------------------
- //这个方法很简单就是调用initialValue()获取初始值(默认值),而且要赋值在当前线程的map中(相当于调用了set()方法)
- 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;
- }
- //ThreadLocal 的默认实现是返回null 的,所以如果在多个线程中使用时,如果需要用到默认值,那子类就应该覆盖该值
- protected T initialValue() {
- return null;
- }
3、public void remove() 方法
- //这个方法没什么可说的,就是删除掉当前线程中的变量(释放内存)
- public void remove() {
- ThreadLocalMap m = getMap(Thread.currentThread());
- if (m != null)
- m.remove(this);
- }
总结
ThreadLocal 可以实现在同一个线程内共享一个变量,不同的线程不能共享,只能得到该变量的一个副本(就是一个默认的初始化值)。
在论坛中也有人提到ThreadLocal和synchronized 的问题,我觉得它们都是解决线程的两个不同的方案。ThreadLocal解决的是同一个线程内的资源共享问题,而synchronized 解决的是多个线程间的资源共享问题。
有关的讨论在:http://www.iteye.com/topic/179040
有关这个类的应用,可以参考http://zhxing.iteye.com/admin/blogs/306070
3. ThreadLocal在JavaEE中的应用
- 一个service方法中只有单个dao操作且此操作是一个select类的操作,请使用openSession,并且即时在finally块中关闭它;
- 如果一个service方法中涉及到多个dao操作,请一定使用getCurrentSession;
- 如果一个service方法中混合着select操作,delete, update, insert操作。请按照下述原则:
a. 将属于select的操作,单独做成一个dao方法,该dao使用openSession并且在finally块中及时关闭session,
b. 对于其它的delete, insert, update的dao操作,请使用getCurrentSession。
c. 忌讳,把select类的操作放在“事务”中;
4. openSession与getCurrentSession的区别
a. openSession一旦被调用,必须且一定要在finally块中close,要不然你就等着out of memory吧;
b. 如果你使用的是getCurrentSession,那么你不能在finally块中调用”session.close()”,不行你可以在finally块中用try-catch把session.close();包起来,然后在catch{}块中抛出这个exception,这个exception将会是:sessionhas been already closed。其原因是你用的是getCurrentSession,那么它在session.commit()或者是session.rollback()时就已经调用了一次session.close()了,因此你只要正确放置session.commit()与rollback()即可。另外,你可以在finally块中调用”ThreadLocalSessionContext.unbind(factory);”,以使得当前的事务结束时把session(即dbconnection)还回db connection pool中。
ThreadLocal源代码分析的更多相关文章
- Java ThreadLocal 源代码分析
Java ThreadLocal 之前在写SSM项目的时候使用过一个叫PageHelper的插件 可以自动完成分页而不用手动写SQL limit 用起来大概是这样的 最开始的时候觉得很困惑,因为直接使 ...
- 多线程之美2一ThreadLocal源代码分析
目录结构 1.应用场景及作用 2.结构关系 2.1.三者关系类图 2.2.ThreadLocalMap结构图 2.3. 内存引用关系 2.4.存在内存泄漏原因 3.源码分析 3.1.重要代码片段 3. ...
- 转:SDL2源代码分析
1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...
- Parrot源代码分析之海贼王
我们的目的是找到speedup-example在使用Parrot加速的原因,假设仅仅说它源于Context Switch的降低,有点简单了,它究竟为什么降低了?除了Context Switch外是否还 ...
- SDL2源代码分析1:初始化(SDL_Init())
===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...
- SDL2源代码分析
1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...
- struts2请求过程源代码分析
struts2请求过程源代码分析 Struts2是Struts社区和WebWork社区的共同成果.我们甚至能够说,Struts2是WebWork的升级版.他採用的正是WebWork的核心,所以.Str ...
- android-plugmgr源代码分析
android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...
- Twitter Storm源代码分析之ZooKeeper中的目录结构
徐明明博客:Twitter Storm源代码分析之ZooKeeper中的目录结构 我们知道Twitter Storm的所有的状态信息都是保存在Zookeeper里面,nimbus通过在zookeepe ...
- 转:RTMPDump源代码分析
0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...
随机推荐
- docker启动问题: Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details.
系统环境:centos 7 docker版本:Docker version 26.1.4, build 5650f9b 问题:Job for docker.service failed because ...
- Angular 18+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)
前言 在 Attribute Directives 属性型指令 文章中,我们学习过了指令.指令是没有 HTML 和 CSS 的组件,它单纯用于封装 JS 的部分. 这一篇我们将继续学习另一种指令 -- ...
- JavaScript – Object.groupBy & Map.groupBy
前言 group by 是一个很常见的功能,但 JS 却没有 build-in 的方法,一直到 es2024 才有 Object.groupBy (前生是 Array.prototype.group) ...
- JavaScript – Decimal
前言 之前就写过一篇 decimal, double, float, 但有点杂乱, 这篇把 JS 的部分独立写成一篇整理版. 参考 JavaScript 浮点数运算的精度问题 关于JavaScript ...
- Identity – User Login, Forgot Password, Reset Password, Logout
前言 这篇来聊聊常见操作. 会讲到: Create Account Login Logout Change Password Reset Password (by email) External Lo ...
- 推荐3款卓越的 .NET 开源搜索组件库
前言 最近有不少同学提问:.NET有哪些开源的搜索组件库可以推荐的吗?,今天大姚给大家推荐3款卓越的 .NET 开源搜索组件库,希望可以帮助到有需要的同学. Elasticsearch .NET El ...
- docker安装及基本的镜像拉取
docker 使用存储库安装 卸载它们以及相关的依赖项. yum remove docker \ docker-client \ docker-client-latest \ docker-commo ...
- MQ核心作用异步&削峰&解耦使用场景详解
说在前面 在如今的高并发互联网应用中,如何确保系统在巨大的流量冲击下还能稳稳当当运转,是每个技术团队都会遇到的挑战.说到这,消息队列(MQ) 就是背后的"大功臣"了. 无论是异步处 ...
- UsbHostManager解析
UsbHostManager和UsbDeviceManager的区别在于,UsbDeviceManager是将手机作为一个设备,比如手机连上电脑,使用adb.mtp等:而UsbHostManager, ...
- 03-jsx中使用js表达式
// 在jsx中使用 js 表达式 /// 通过一个 {} 展示变量即可 vue 中使用 {{}} 展示js表达式 // 什么是js表达式 有结果的 import reactDom from &quo ...