关于 ThreadLocal ,源码读起来有些费劲 ------ 如果你对它的原理事先没有一个大概的了解的话。网上有很多文章模棱两可,甚至有不少错误。有幸看到这位作者的博客,搬过来备忘一下。
原文地址:
正确理解 ThreadLocal
首先, ThreadLocal 不是用来解决共享对象的多线程访问问题的。一般情况下,通过 ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说 ThreadLocal 使得各线程能够保持各自独立的一个对象,并不是通过 ThreadLocal.set() 来实现的,而是通过每个线程中的“new 对象”的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过 ThreadLocal.set() 将这个新创建的对象的引用保存到各线程的自己的一个 map 中,每个线程都有这样一个 map ,执行 ThreadLocal.get() 时,各线程从自己的 map 中取出放进去的对象,因此取出来的是各自自己线程中的对象, ThreadLocal 实例是作为 map 的 key 来使用的。
如果 ThreadLocal.set() 进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get() 取得的还是这个共享对象本身,还是有并发访问问题。
下面来看一个 Hibernate 中典型的 ThreadLocal 的应用:
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
可以看到在 getSession() 方法中,首先判断当前线程中有没有放进去 session ,如果还没有,那么通过 getSessionFactory().openSession() 来创建一个 session ,再将 session set 到线程中,实际是放到当前线程的 ThreadLocalMap 这个 map 中。这时,对于这个 session 的唯一引用就是当前线程中的那个 ThreadLocalMap (下面会讲到),而 threadSession 作为这个值的 key ,要取得这个 session 可以通过 threadSession.get() 来得到,里面执行的操作实际是先取得当前线程中的 ThreadLocalMap ,然后将 threadSession 作为 key 将对应的值取出。这个 session 相当于线程的私有变量,而不是 public 的。
显然,其他线程中是取不到这个 session 的,他们也只能取到自己的 ThreadLocalMap 中的东西。要是 session 是多个线程共享使用的,那还不乱套了。
试想如果不用 ThreadLocal 怎么来实现呢?可能就要在 action 中创建 session ,然后把 session 一个个传到 service 和 dao 中,这可够麻烦的。或者可以自己定义一个静态的 map ,将当前 thread 作为 key ,创建的 session 作为值, put 到 map 中,应该也行,这也是一般人的想法。但事实上, ThreadLocal 的实现刚好相反,它是在每个线程中有一个 map ,而将 ThreadLocal 实例作为 key ,这样每个 map 中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。
总之, ThreadLocal 不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1. 每个线程中都有一个自己的 ThreadLocalMap 类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2. 将一个共用的 ThreadLocal 静态实例作为 key ,将不同对象的引用保存到不同线程的 ThreadLocalMap 中,然后在线程执行的各处通过这个静态 ThreadLocal 实例的 get() 方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
当然如果要把本来线程共享的对象通过 ThreadLocal.set() 放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意 get() 到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。
ThreadLocal 的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
下面来看看 ThreadLocal 的实现原理(jdk1.5源码)
public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread hash maps attached to each thread
* (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
* objects act as keys, searched via threadLocalHashCode. This is a
* custom hash code (useful only within ThreadLocalMaps) that eliminates
* collisions in the common case where consecutively constructed
* ThreadLocals are used by the same threads, while remaining well-behaved
* in less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Accessed only by like-named method.
*/
private static int nextHashCode = 0;
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Compute the next hash code. The static synchronization used here
* should not be a performance bottleneck. When ThreadLocals are
* generated in different threads at a fast enough rate to regularly
* contend on this lock, memory contention is by far a more serious
* problem than lock contention.
*/
private static synchronized int nextHashCode() {
int h = nextHashCode;
nextHashCode = h + HASH_INCREMENT;
return h;
}
/**
* Creates a thread local variable.
*/
public ThreadLocal() {
}
/**
* Returns the value in the current thread's copy of this thread-local
* variable. Creates and initializes the copy if this is the first time
* the thread has called this 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)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Many applications will have no need for
* this functionality, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current threads' 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);
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
.......
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
........
}
}
可以看到ThreadLocal类中的变量只有这3个int型:
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode = 0;
private static final int HASH_INCREMENT = 0x61c88647;
而作为 ThreadLocal 实例的变量只有 threadLocalHashCode 这一个, nextHashCode 和 HASH_INCREMENT 是 ThreadLocal 类的静态变量,实际上 HASH_INCREMENT 是一个常量,表示了连续分配的两个 ThreadLocal 实例的 threadLocalHashCode 值的增量,而 nextHashCode 则表示了即将分配的下一个 ThreadLocal 实例的 threadLocalHashCode 的值。
可以来看一下创建一个 ThreadLocal 实例即 new ThreadLocal() 时做了哪些操作,从上面看到构造函数ThreadLocal() 里什么操作都没有,唯一的操作是这句:
private final int threadLocalHashCode = nextHashCode();
那么 nextHashCode() 做了什么呢:
private static synchronized int nextHashCode() {
int h = nextHashCode;
nextHashCode = h + HASH_INCREMENT;
return h;
}
就是将 ThreadLocal 类的下一个 hashCode 值即 nextHashCode 的值赋给实例的threadLocalHashCode ,然后 nextHashCode 的值增加 HASH_INCREMENT 这个值。
因此 ThreadLocal 实例的变量只有这个 threadLocalHashCode ,而且是 final 的,用来区分不同的 ThreadLocal 实例。 ThreadLocal 类主要是作为工具类来使用,那么 ThreadLocal.set() 进去的对象是放在哪儿的呢?
看一下上面的 set() 方法,两句合并一下成为:
ThreadLocalMap map = Thread.currentThread().threadLocals;
这个 ThreadLocalMap 类是 ThreadLocal 中定义的内部类,但是它的实例却用在 Thread 类中:
public class Thread implements Runnable {
......
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
......
}
再看这句:
if (map != null)
map.set(this, value);
也就是将该 ThreadLocal 实例作为 key ,要保持的对象作为值,设置到当前线程的 ThreadLocalMap 中。 get()方法同样大家看了代码也就明白了, ThreadLocalMap 类的代码太多了,我就不帖了,自己去看源码吧。
最后转载这篇文章的“作者”我再罗嗦一句: ThreadLocal 的关键是:以线程共享的 ThreadLocal 实例本身作为 key ,把 set 进去的值放在当前线程的 ThreadLocalMap 中,从而实现线程隔离。
分享到:
相关推荐
### 正确理解ThreadLocal:深入解析其工作原理与应用场景 #### 一、ThreadLocal的基本概念 `ThreadLocal`是Java平台提供的一种线程局部变量的解决方案,它为每一个使用该变量的线程都提供了独立的变量副本,使得每...
**标题:“正确理解ThreadLocal”** ThreadLocal是Java并发编程中的一个重要工具类,它提供了一种线程局部变量的机制。这些变量在每个线程中都有独立的副本,互不干扰,可以用来解决多线程环境下的数据隔离问题。...
### ThreadLocal核心概念详解 #### 一、ThreadLocal简介与重要性 ThreadLocal是一个非常重要的Java并发工具类,它的核心概念在于为每一个...理解ThreadLocal的工作原理对于编写高效、安全的多线程应用程序至关重要。
1. 多线程:理解ThreadLocal的使用必须建立在对多线程的理解基础上,包括线程的创建、执行、同步机制等。 2. 并发编程:ThreadLocal是解决并发问题的一种策略,它提供了一种避免共享状态的方式,减少了锁的使用。 3....
本资料主要聚焦于两种设计模式以及Java中的ThreadLocal特性。 首先,我们来探讨单例模式。单例模式是一种确保一个类只有一个实例,并提供全局访问点的设计模式。在Java中,通常通过私有构造函数、静态工厂方法或...
ThreadLocal是Java编程中一种非常特殊的变量类型,它主要...正确使用ThreadLocal可以提高代码的并发性能,而滥用则可能导致难以预料的问题。因此,在使用ThreadLocal时,需要充分考虑其生命周期管理和线程安全问题。
at 中专门为每一个 web 应用...理解 `ThreadLocal` 的工作原理以及它如何与类加载器交互,是避免此类问题的关键。在实际开发中,应当养成良好的编程习惯,如使用后及时清理 `ThreadLocal` 变量,以防止内存资源的浪费。
需要注意的是,尽管ThreadLocal提供了线程局部的变量,但如果不正确地使用,比如在ThreadLocal生命周期结束后没有清理ThreadLocalMap,可能会导致内存泄漏。因此,最佳实践是在不再需要ThreadLocal时调用remove方法...
在实际应用中,理解ThreadLocal在Spring事务处理中的作用有助于优化并发性能和解决多线程环境下的事务问题。例如,如果线程之间需要共享数据,但又不想影响其他线程,ThreadLocal就是一个理想的选择。同时,也要注意...
- **内存泄漏**:如果`ThreadLocal`没有被正确清理,当线程生命周期结束但引用仍然存在时,可能导致内存泄漏。因此,通常在不再需要`ThreadLocal`时,应该调用`remove`方法。 - **性能影响**:过度使用`ThreadLocal`...
其他说明:为了更好地理解ThreadLocal的工作机制,建议实际动手尝试文中提供的实例代码,同时注意不同版本JDK之间的差异可能会导致部分细节有所变化。对于追求高性能并发应用开发的技术人员而言,了解和运用...
让我们通过一个简单的例子来理解`ThreadLocal`的工作机制: ```java public class ThreadLocalExample { private static ThreadLocal<Integer> threadLocal = new ThreadLocal(); public static void main...
ThreadLocal 翻译过来就是本地线程,但是直接这么翻译很难理解 ThreadLocal 的作用。如果换一种说法,可以称为线程本地存储。简单来说,就是 ThreadLocal 为共享变量在每个线程中都创建一个副本,每个线程可以访问...
理解ThreadLocal的工作原理以及如何正确使用它是Java并发编程的关键。 首先,ThreadLocal类的主要作用是为每个线程创建一个单独的变量副本。这意味着在同一个ThreadLocal变量中,不同的线程可以存储各自独立的值,...
Python中的ThreadLocal,全称是`threading.local`,是一个非常重要的并发编程工具。它主要用于在多线程环境中保持线程局部变量,确保每个...正确理解和使用ThreadLocal能够帮助开发者编写出更加健壮和高效的并发程序。
测试用例可以帮助我们更好地理解ThreadLocal的使用和潜在问题: - 创建多个线程,每个线程使用ThreadLocal变量,如果变量引用的是同一个共享对象,修改其中一个线程的变量值,其他线程是否也会看到改变,以此来观察...
在本文中,我们将详细介绍Java ThreadLocal的使用案例,并通过一个实际的优化案例,帮助大家理解ThreadLocal的使用。 ThreadLocal的使用场景 在Java多线程编程中,变量的线程安全是一个非常重要的问题。特别是在...