目录

Threadlocal

ThreadLocal 提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。

ThreadLocal 是用来处理多线程并发问题的一种解决方案。在多线程并发环境下,提供了与其他线程隔离的局部变量。

ThreadLocal 类中有一个静态内部类 ThreadLocalMap,每一个线程 Thread 持有一个 ThreadLocalMap,键为该线程对象,而值对应线程的变量副本。

void set(T value)ThreadLocalMap map = Thread.currentThread().threadLocals;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
static class ThreadLocalMap {
    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

ThreadLocal 源码

public void set(T value) 设置当前线程的线程局部变量的值

  • 获得当前线程对象 Thread
  • 取出当前线程对象的成员变量 ThreadLocalMap
  • key 是 ThreadLocal ,value是设置的值存放到 ThreadLocalMap

public T get() 返回当前线程所对应的线程局部变量

  • 获得当前线程对象 Thread
  • 取出当前线程对象的成员变量 ThreadLocalMap
  • ThreadLocalMap 中获取当前线程对应的值,如果为null,返回 private T setInitialValue() 其实就是通过重载 T initialValue() 获取的值

public void remove() 将当前线程局部变量的值删除

目的是为了减少内存的占用,该方法是JDK5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected T initialValue() 返回该线程局部变量的初始值

该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

ThreadLocal 副作用

虽然,ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象,这个时候ThreadLocal就失效了。

Thread 数量一般来说比ThreadLocal数量多,所以 ThreadLocalMap 放在Thread 中

当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在 ThreadLocalMap Entry<nullObject>的键值对,造成内存泄露。

虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,有两种手段。

  • 使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
  • JDK 建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。

ThreadLocalMap 中的 key 使用 ThreadLocal 的弱引用,但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。

事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。也可以通过调用ThreadLocal的remove方法进行释放!

  • WeakReference 的引入,是为了将ThreadLocal 对象与ThreadLocalMap 设计成一种弱引用的关系,来避免ThreadLocal 实例对象不能被回收而存在的内存泄露问题,当threadLocal 对象被回收时,会有清理 stale entry 机制,回收其对应的Value实例对象。
  • 我们常说的内存泄露问题,针对的是threadLocal对应的Value对象实例。在线程对象被重用且threadLocal为静态变量时,如果没有手动remove(),就可能会造成内存泄露的情况。
  • 上述两种内存泄露的情况只有在线程复用的情况下才会出现,因为在线程销毁时threadLocalMap的对象引用会被置为null。
  • 解决副作用的方法很简单,就是每次用完ThreadLocal,都要及时调用 remove() 方法去清理。

使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ConnectionManager {
	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
  @Override
		protected Connection initialValue() {
			Connection conn = null;
			try {
				conn = DriverManager.getConnection(
						"jdbc:mysql://localhost:3306/test", "username",
						"password");
			} catch (SQLException e) {
				e.printStackTrace();
			}
			return conn;
		}
	};

	public static Connection getConnection() {
		return connectionHolder.get();
	}

	public static void setConnection(Connection conn) {
		connectionHolder.set(conn);
	}
}
// Hibernate 中的 Session
// hibernate.cfg.xml 核心配置文件
public class SessionManager {
    private static final ThreadLocal<Session> threadSession = new ThreadLocal<>();
	public static Session getSession() throws InfrastructureException {
    Session s = threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
	}
}

InheritableThreadLocal

在一些场景中,子线程需要可以获取父线程的本地变量,比如用一个统一的ID来追踪记录调用链路。但是ThreadLocal 是不支持继承性的,同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到对应的对象的。 为了解决这个问题,InheritableThreadLocal 也就应运而生。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class InheritableThreadLocalDemo {    
    private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();    
    public static void main(String[] args) {        
        // 主线程        
        threadLocal.set("hello world");        
        // 启动子线程        
        Thread thread = new Thread(() -> {            
        // 子线程输出父线程的threadLocal 变量值            
        System.out.println("子线程: " + threadLocal.get());      
        });        
        thread.start();        
        System.out.println("main: " + threadLocal.get());    
    }
}

这个类能让子线程继承父线程中已经设置的ThreadLocal值。

需要注意的是一旦子线程被创建以后,再操作父线程中的ThreadLocal变量,那么子线程是不能感知的。因为父线程和子线程还是拥有各自的ThreadLocalMap,只是在创建子线程的“一刹那”将父线程的ThreadLocalMap复制给子线程,后续两者就没啥关系了。

ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的作用。

在Spring 事务管理中的应用

在事务管理中,在服务类中的涉及到事务的方法,每个事务的上下文都应该是独立拥有数据库的 Connection连接的,否则在数据提交回滚过程中就会产生冲突。

Spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。

DataSourceTransactionManager的实现中,doBegin()方法开启事务。

首先从数据库连接池中获得一个Connection 实例,并构造一个 ConnectionHolder 包装类实例,使用这个包装类开启事务,最后通过 TransactionSynchronizationManager.bindResource() 将Connection 实例与ThreadLocal绑定,事务提交或者回滚后,解除绑定 unbindResource()

1
2
3
4
package org.springframework.transaction.support;
public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
}

小结

ThreadLocal 实现线程内部变量共享,InheritableThreadLocal 实现了父线程与子线程的变量继承。但是还有一种场景,InheritableThreadLocal 无法解决,也就是在使用线程池等会池化复用线程的执行组件情况下,异步执行执行任务,需要传递上下文的情况。

针对上述情况,阿里开源了一个TTL库,即 Transmittable ThreadLocal 来解决这个问题。