ThreadLocal原理及其实际应用 - format丶

标签: threadlocal 原理 其实 | 发表时间:2014-11-23 12:21 | 作者:format丶
出处:

前言

java猿在面试中,经常会被问到1个问题:
java实现同步有哪几种方式?

大家一般都会回答使用synchronized, 那么还有其他方式吗? 答案是肯定的, 另外一种方式也就是本文要说的ThreadLocal。

ThreadLocal介绍

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).

粗略地翻译一下:
ThreadLocal这个类提供线程本地的变量。这些变量与一般正常的变量不同,它们在每个线程中都是独立的。ThreadLocal实例最典型的运用就是在类的私有静态变量中定义,并与线程关联。

什么意思呢? 下面我们通过1个实例来说明一下:

jdk中的SimpleDateFormat类不是一个线程安全的类,在多线程中使用会出现问题,我们会通过线程同步来处理:

  1. 使用synchronized

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public synchronized static String formatDate(Date date) {
    return sdf.format(date);
    }
  2. 使用ThreadLocal

    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
    @Override
    protected SimpleDateFormat initialValue()
    {
    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
    };

    public String formatIt(Date date)
    {
    return formatter.get().format(date);
    }

这两种方式是一样的,只不过一种用了synchronized,另外一种用了ThreadLocal。

synchronized和ThreadLocal的区别

使用synchronized的话,表示当前只有1个线程才能访问方法,其他线程都会被阻塞。当访问的线程也阻塞的时候,其他所有访问该方法的线程全部都会阻塞,这个方法相当地 "耗时"。
使用ThreadLocal的话,表示每个线程的本地变量中都有SimpleDateFormat这个实例的引用,也就是各个线程之间完全没有关系,也就不存在同步问题了。

综合来说:使用synchronized是一种 "以时间换空间"的概念, 而使用ThreadLocal则是 "以空间换时间"的概念。

ThreadLocal原理分析

我们先看下ThreadLocal的类结构:

我们看到ThreadLocal内部有个ThreadLocalMap内部类,ThreadLocalMap内部有个Entry内部类。

先介绍一下ThreadLocalMap和ThreadLocalMap.Entry内部类:
ThreadLocalMap其实也就是一个为ThreadLocal服务的自定义的hashmap类。
Entry是一个继承WeakReference类的类,也就是ThreadLocalMap这个hash map中的每一项,并且Entry中的key基本上都是ThreadLocal。

再下来我们看下Thread线程类:
Thread线程类内部有个ThreadLocal.ThreadLocalMap类型的属性:

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

下面重点来看ThreadLocal类的源码:

public T get() {
// 得到当前线程
Thread t = Thread.currentThread();
// 拿到当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 找到该ThreadLocal对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 当前线程没有ThreadLocalMap对象的话,那么就初始化ThreadLocalMap
return setInitialValue();
}

private T setInitialValue() {
// 初始化ThreadLocalMap,默认返回null,可由子类扩展
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 实例化ThreadLocalMap之后,将初始值丢入到Map中
map.set(this, value);
else
createMap(t, value);
return value;
}

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

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

public void set(T value) {
// set逻辑:找到当前线程的ThreadLocalMap,找到的话,设置对应的值,否则创建ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

注释已经写了,读者有不明白的可以自己看看源码。

ThreadLocal的应用

ThreadLocal应用广泛,下面介绍下在SpringMVC中的应用。

RequestContextHolder内部结构

RequestContextHolder:该类会暴露与线程绑定的RequestAttributes对象,什么意思呢? 就是说web请求过来的数据可以跟线程绑定, 用户A,用户B分别请求过来,可以使用RequestContextHolder得到各个请求的数据。

RequestContextHolder数据结构:

具体这两个holder:

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");

private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");

这里的NamedThreadLocal只是1个带name属性的ThreadLocal:

public class NamedThreadLocal<T> extends ThreadLocal<T> {

private final String name;

public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}

@Override
public String toString() {
return this.name;
}

}

继续看下RequestContextHolder的getRequestAttributes方法,其中接口RequestAttributes是对请求request的封装:

public static RequestAttributes getRequestAttributes() {
// 直接从ThreadLocalContext拿当前线程的RequestAttributes
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}

我们看到,这里直接使用了ThreadLocal的get方法得到了RequestAttributes。
当需要得到Request的时候执行:

ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();

RequestContextHolder的初始化

以下代码在FrameworkServlet代码中:


总结

本文介绍了ThreadLocal的原理以及ThreadLocal在SpringMVC中的应用。个人感觉ThreadLocal应用场景更多是共享一个变量,但是该变量又不是线程安全的,而不是线程同步。比如RequestContextHolder、LocaleContextHolder、SimpleDateFormat等的共享应用。


本文链接: ThreadLocal原理及其实际应用,转载请注明。

相关 [threadlocal 原理 其实] 推荐:

ThreadLocal原理及其实际应用 - format丶

- - 博客园_首页
java猿在面试中,经常会被问到1个问题:. java实现同步有哪几种方式. 大家一般都会回答使用synchronized, 那么还有其他方式吗. 答案是肯定的, 另外一种方式也就是本文要说的ThreadLocal. ThreadLocal介绍. ThreadLocal, 看名字也能猜到, "线程本地", "线程本地变量".

ThreadLocal介绍

- - ITeye博客
一、java.lang.ThreadLocal. 一个实例就是一个容器,所有可以访问到这个实例的线程都可以在这个容器中存储一个该线程独立使用的变量. 这个实例里面其实是一个Map结构的属性,存储以线程对象为KEY,变量为VALUE的数据. 二、ThreadLocal有这样几个方法:. 返回当前线程对应的那个变量.

正确理解ThreadLocal

- - Java - 编程语言 - ITeye博客
转自: http://www.iteye.com/topic/103804. 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.

ThreadLocal的内存泄露

- - zzm
ThreadLocal的目的就是为每一个使用ThreadLocal的线程都提供一个值,让该值和使用它的线程绑定,当然每一个线程都可以独立地改变它绑定的值. 如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序.. 关于的ThreadLocal更多内容,请参考《 ThreadLocal》.

(转)ThreadLocal的内存泄漏问题

- - 编程语言 - ITeye博客
原文:http://www.godiscoder.com/?p=479. 在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题. 表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出. 开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放.

ThreadLocal解决dateFormat多线程错误

- - 研发管理 - ITeye博客
出处  http://www.blogjava.net/killme2008/archive/2011/07/10/354062.html.     上周在线上系统发现了两个bug,值得记录下查找的过程和原因. 以后如果还有查找bug比较有价值的经历,我也会继续分享.     第一个bug的起始,是在线上日志发现一个频繁打印的异常——java.lang.ArrayIndexOutOfBoundsException.

java 使用动态代理 - ThreadLocal实现事务管理

- - ITeye博客
动态代理:JDK动态代理只能对实现了接口的类进入代理,采用JDK动态代理必须实现InvocationHandler接口,采用Proxy 类创建相应的代理类.. 下面使用Model2(MVC)使用代理事务查询用户基本信息,使用DB2数据库:. 建立表: create table T_USER (. constraint P_KEY_1 primary key (USER_ID) ); 初始化数据: insert into t_user(user_id, user_name, password) values('root', '系统管理员', 'root');.

POJO中使用ThreadLocal实现Java嵌套事务

- - ImportNew
大多嵌套事务都是通过EJB实现的,现在我们尝试实现对POJO的嵌套事务. 这里我们使用了ThreadLocal的功能. 所以内层事务或外层事务可以在不影响其他事务的条件下进行回滚或提交. 新建的事务嵌套在外层事务中. 如果内层事务完成(不论是回滚或是提交),外层的事务就可以进行回滚或提交,这样的操作并不会影响内层事务.

四位计算机的原理及其实现

- frocket - 阮一峰的网络日志
你是否想过,计算机为什么会加减乘除. 或者更直接一点,计算机的原理到底是什么. Waitingforfriday有一篇详细的教程,讲解了如何自己动手,制作一台四位计算机. 从中可以看到,二进制、数理逻辑、电子学怎样融合在一起,构成了现代计算机的基础. 计算机内部采用二进制,每一个数位只有两种可能"0"和"1",运算规则是"逢二进一".

HandlerSocket的原理

- Roger - MySQLOPS 数据库与运维自动化技术分享
HandlerSocket的应用场景:. MySQL自身的局限性,很多站点都采用了MySQL+Memcached的经典架构,甚至一些网站放弃MySQL而采用NoSQL产品,比如Redis/MongoDB等. 不可否认,在做一些简单查询(尤其是PK查询)的时候,很多NoSQL产品比MySQL要快很多,而且前台网站上的80%以上查询都是简洁的查询业务.