前言

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原理及其实际应用的更多相关文章

  1. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  2. 简析ThreadLocal原理及应用

    简析ThreadLocal原理及应用 原创: 东晨雨 JAVA万维猿圈 4月17日 ThreadLocal的源码加上注释不超过八百行,源码结构清晰,代码也比较简洁.ThreadLocal可以说是Jav ...

  3. ThreadLocal原理简单刨析

    ThreadLocal原理简单刨析 ThreadLocal实现了各个线程的数据隔离,要知道数据是如何隔离的,就要从源代码分析. ThreadLocal原理 需要提前说明的是:ThreadLocal只是 ...

  4. ThreadLocal原理与模拟

    首先用一个程序模拟一下ThreadLocal: public class ThreadLocal1 { private static Dictionary<Thread, Integer> ...

  5. java concurrency in practice读书笔记---ThreadLocal原理

    ThreadLocal这个类很强大,用处十分广泛,可以解决多线程之间共享变量问题,那么ThreadLocal的原理是什么样呢?源代码最能说明问题! public class ThreadLocal&l ...

  6. ThreadLocal原理及使用示例

    简介:本文已一个简要的代码示例介绍ThreadLocal类的基本使用方式,在此基础上结合图片阐述它的内部工作原理. 欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblog ...

  7. 聊聊ThreadLocal原理以及使用场景-JAVA 8源码

    相信很多人知道ThreadLocal是针对每个线程的,但是其中的原理相信大家不是很清楚,那咱们就一块看一下源码. 首先,我们先看看它的set方法.非常简单,从当前Thread中获取map.那么这个ge ...

  8. ThreadLocal 原理和使用场景分析

    ThreadLocal 不知道大家有没有用过,但至少听说过,今天主要记录一下 ThreadLocal 的原理和使用场景. 使用场景 直接定位到 ThreadLocal 的源码,可以看到源码注释中有很清 ...

  9. ThreadLocal原理分析与使用场景

    什么是ThreadLocal变量 ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本.这里有几点需要注意: 因为每个 Thr ...

随机推荐

  1. 16、总经理要阅读的书籍 - IT软件人员书籍系列文章

    总经理是公司的一个领导角色.他主要负责公司级别的比如规划,战略等等内容.有些公司的总经理,比如软件公司的总经理,往往是一个大的业务员,将更多的大型的软件项目投取过来,让公司能够有钱赚,让公司员工能够跟 ...

  2. 各类 HTTP 返回状态代码详解

    完整版 1**(信息类):表示接收到请求并且继续处理 100——客户必须继续发出请求 101——客户要求服务器根据请求转换HTTP协议版本 2**(响应成功):表示动作被成功接收.理解和接受 200— ...

  3. pentaho cde 画图参数介绍

    初步接触pentaho,由于在国内的资料很少,唯有看英文文档,做了N次反复尝试,挖掘了pentaho CDE中画图的一些基本参数. 下面就列出来了一些常用参数介绍: crosstabMode:表明如果 ...

  4. Java并发之CountDownLatch 多功能同步工具类

    package com.thread.test.thread; import java.util.Random; import java.util.concurrent.*; /** * CountD ...

  5. MongoDB学习——基础入门

    MongoDB--基础入门 MongoDB是目前比较流行的一种非关系型数据库(NoSql),他的优势这里不废话,我们关注怎么使用它. 安装 下载,首先肯定要去下载,我们去官网下载,在国内,可能没FQ可 ...

  6. SQL Server 分隔字符串函数实现

    在SQL Server中有时候也会遇到字符串进行分隔的需求.平时工作中常常遇到这样的需求,例如:人员数据表和人员爱好数据表,一条人员记录可以多多人员爱好记录,而往往人员和人员爱好在界面展示层要一并提交 ...

  7. 烂泥:linux文件同步之rsync学习(一)

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 这几天刚好有空就打算开始学习linux下的文件同步软件rsync,在学习rsync时,我们可以分以下几个步骤进行: 1. rsync是什么 2. rsy ...

  8. RedHat Linux 9.0的安装+入门指南(图文并茂)

    一,准备工作1,购买或下载Redhat9的安装光盘(3张盘)或镜像文件2,在硬盘中至少留2个分区给安装系统用,挂载点所用分区推荐4G以上,交换分区不用太大在250M左右比较适合,文件系统格式不论,反正 ...

  9. 工作中常用的Linux命令:ipcs/ipcrm命令

    本文链接:http://www.cnblogs.com/MartinChentf/p/6057100.html (转载请注明出处) ipcs 1. 命令格式 ipcs [resource-option ...

  10. Eclipse快捷键-方便查找

    [Ct rl+T] 搜索当前接口的实现类 1. [ALT +/]    此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不全方法和属性名称犯愁,当记不全类.方法和属性的名字时,多体验一下[ ...