`
mabusyao
  • 浏览: 247345 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

初识ThreadLocal

 
阅读更多

最近公司在进行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):
/**
 * 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);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}
 
可以看到,当ThreadLocal的get方法被调用时,首先利用当前线程作为key获得了一个map,而这个map便是当前线程专属的,其它线程无法访问。在从该Map中找到相应的对象并返回。
 
而set方法正好相反:
/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to 
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
 

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

private static ThreadLocal<Connection> connectionHolder
       = new ThreadLoca<Connection>() {

          public Connection initialValue() {
               return DriverManager.getConnection(DB_URL);
          }
     };
public static Connection getConnection() {
     return ConnectionHolder.get();
}

 

 

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

 

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

 

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics