初识ThreadLocal

原创 2015年07月07日 14:15:26

最近公司在进行Java开发人员的招聘活动,其中有一道面试题是这样的:“请简单描述一下ThreadLocal类的作用。” 结果发现有很多的面试者没有听说过ThreadLocal或者听说过却不知道这个类究竟是用来做什么的。 因此这里写一篇博客来介绍一下ThreadLocal这个类。

 

在我们日常的项目开发中,ThreadLocal并不是一个经常使用的类。它更多的是被用在诸如Spring,Tomcat或者是Hibernate这些封装了多线程并发的框架或是容器中。而它的目的也正是为了解决多线程并发访问共享数据的问题。
 
尽管普通开发人员很少有机会涉及到它,了解ThreadLocal也依然有助于他们来学习Java并发编程。通过阅读ThreadLocal的源码并了解它解决并发问题的思路,开发人员可以更好的理解代码中遇到的多线程bug,更不用提那些在项目开发中需要用到多线程编程的开发人员了。因此,不论你是否用到了ThreadLocal类,都很有必要学习一下它。
 
在我们讨论代码细节之前,先来看看java concurrent in practice中对于多线程并发问题的描述:
     “所有的多线程问题都可以归结为多个线程访问共享可变状态时的管理问题。”
 
这里的状态也就我们说的数据。这句话说明多线程问题必须在以下三个条件都满足的时候才会发生:
1. 拥有多个线程
2. 共享状态
3. 该状态可变
 
如果其中任何一个条件没有办法满足,都不会出现多线程问题:
1. 只有单一的线程。 很显然,这并没有多线程问题。

2. 共享状态不可变。 假设某条数据被多线程共享,然而该数据是不可变数据,那么它便没有多线程问题。举例来说: Java中的String类型就是不可变的,因此String的共享并不会导致多线程安全问题。

3. 多线程不共享状态。 任意的数据都由某个线程独占,不与其他线程分享,因此也不会出现多线程问题。
 
那么相应的,解决多线程问题的办法有以下几种:
1. 在访问状态变量时使用同步。这是最基本的想法,任何一本Java多线程编程的书都会详细描述如何在Java中使用同步,这里不再赘述。
2. 将状态变量修改为不可变的变量。许多新的编程语言,诸如Scala,便是采用这样的办法来解决多线程问题的。
3. 避免线程之间共享状态变量。 我们今天讨论的ThreadLocal,便是属于此类解决办法。
 
刚刚接触ThreadLocal的同学经常会问这样一个问题:“ThreadLocal是线程安全的么?” 这个问题很难回答,因为当你问这个问题的时候,便默认的认为ThreadLocal是为了解决多线程之间共享状态的访问问题的。虽然ThreadLocal的目的正是如此,但是它所采用的办法是“避免多线程之间共享状态”。既然没有了多线程的共享状态,也就无所谓是否线程安全了。因此不能简单的说ThreadLocal是否线程安全,这个问题其实没有意义。
那么ThreadLocal是如何做到“避免多线程之间的状态共享”的呢?通过在内部维护一个(当前线程 ->对象)的映射表,每个线程都只能访问到映射到自己线程的对象,而无法访问其它线程的对象。通过这种方法,避免了多线程之间的状态共享,自然也就无所谓线程安全问题了。
 
如果你将某个对象的引用扩散到多个线程中,并将其设置到ThreadLocal里,那么多个线程所指向的便是同一个对象,对它的访问当然也是有线程安全问题的。从这个角度来讲,ThreadLocal并不是线程安全的。
 
换一个角度来描述: TheadLocal并没有真正解决多线程共享状态的安全问题,它只是通过避免状态共享的办法规避了多线程安全问题。
 
我们来看一下ThreadLocal的源码(JDK1.6):
Java代码  收藏代码
  1. /** 
  2.  * Returns the value in the current thread's copy of this 
  3.  * thread-local variable.  If the variable has no value for the 
  4.  * current thread, it is first initialized to the value returned 
  5.  * by an invocation of the {@link #initialValue} method. 
  6.  * 
  7.  * @return the current thread's value of this thread-local 
  8.  */  
  9. public T get() {  
  10.     Thread t = Thread.currentThread();  
  11.     ThreadLocalMap map = getMap(t);  
  12.     if (map != null) {  
  13.         ThreadLocalMap.Entry e = map.getEntry(this);  
  14.         if (e != null)  
  15.             return (T)e.value;  
  16.     }  
  17.     return setInitialValue();  
  18. }  
 
可以看到,当ThreadLocal的get方法被调用时,首先利用当前线程作为key获得了一个map,而这个map便是当前线程专属的,其它线程无法访问。在从该Map中找到相应的对象并返回。
 
而set方法正好相反:
Java代码  收藏代码
  1. /** 
  2.  * Sets the current thread's copy of this thread-local variable 
  3.  * to the specified value.  Most subclasses will have no need to  
  4.  * override this method, relying solely on the {@link #initialValue} 
  5.  * method to set the values of thread-locals. 
  6.  * 
  7.  * @param value the value to be stored in the current thread's copy of 
  8.  *        this thread-local. 
  9.  */  
  10. public void set(T value) {  
  11.     Thread t = Thread.currentThread();  
  12.     ThreadLocalMap map = getMap(t);  
  13.     if (map != null)  
  14.         map.set(this, value);  
  15.     else  
  16.         createMap(t, value);  
  17. }  
 

我们来看一个在Hibernate中使用ThreadLocal的例子:

Java代码  收藏代码
  1. private static ThreadLocal<Connection> connectionHolder  
  2.        = new ThreadLoca<Connection>() {  
  3.   
  4.           public Connection initialValue() {  
  5.                return DriverManager.getConnection(DB_URL);  
  6.           }  
  7.      };  
  8. public static Connection getConnection() {  
  9.      return ConnectionHolder.get();  
  10. }  

 

 

上面的例子是一个最经典的ThreadLocal使用案例: 在单线程中创建一个单例变量,并在程序启动时初始化该单例变量,从而避免在调用每个方法时都要传递该变量。然而该单例可能并不是线程安全的,因此,当多线程应用程序在没有互相协作的情况下,可以通过将该单例变量保存到ThreadLocal中,以确保每一个线程都拥有属于自己的实例。

 

在这里,一个更好理解的说法便是:该单例是一个线程内单例,在多线程应用中,每个线程里都有一个该单例。

 

通过学习ThreadLocal,我们能够对正确的在项目中使用它,同时,也能够帮助我们对多线程编程有一个更深的认识.


(该文同时发表在http://mabusyao.iteye.com/blog/2224898)

ThreadLocal初识

 初识ThreadLocal 一个例子引发的思考 ThreadLocal 模式是解决并发数据共享的一个典型的方案,spring,struts等经典框架都有用到ThreadLocal 的场景。 ...

实验1 JavaScript初识_实验指导书

  • 2017年11月27日 16:37
  • 182KB
  • 下载

性能测试初识

  • 2015年09月09日 15:53
  • 429KB
  • 下载

关于数据库查询优化之sql随笔(初识)

以前写sql时没有想过太多,觉得能达到效果就好,所以一直没有特别在意什么样的语句应该用在什么场合,直到前一阵子客户总是说什么某某模块查询起来很慢要10几分钟,我才渐渐开始注意这个事情。因为这个项目的数...

算法结构初识

  • 2015年12月08日 16:59
  • 418KB
  • 下载

mybatis初识

  • 2015年08月24日 19:42
  • 41KB
  • 下载

ReactNative源码篇:源码初识

本篇文章是《ReactNative源码篇》的第一篇文章,刚开始,我们先不对源码做深入的分析,我们先要对源码的结构和ReactNative的框架有个大致的印象, 由此便引出了本篇文章需要讨论的两个问题:...

第2章 初识J2EE ppt

  • 2010年04月09日 14:42
  • 642KB
  • 下载

初识c#简介

  • 2013年11月18日 15:26
  • 915KB
  • 下载

Dubbo的学习与总结(一)- -初识

公司要对现有的组织架构重新进行划分,将一些现有的项目根据功能拆分,来应对业务量不断增长对系统的压力。通过比较,最终选择Dubbo作为服务的框架。 主要考虑以下几点: 资料文档比较全,Dubbo是阿里...
  • lf_214
  • lf_214
  • 2016年03月28日 12:05
  • 607
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:初识ThreadLocal
举报原因:
原因补充:

(最多只允许输入30个字)