ThreadLocal是什么
ThreadLocal 是 Java 中的一个工具类,它提供了线程局部变量。意思是,它可以创建变量副本给每个线程使用,而每个线程访问变量副本时都是隔离的,互不干扰。
ThreadLocal 的主要作用有:
线程隔离:每个线程有自己的变量副本,互不影响。
避免传参:通过 ThreadLocal 传递数据,不需要在方法之间传递参数。
来看一个例子:
public class ThreadLocalExample { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public static void add() { threadLocal.set(threadLocal.get() + 1); } public static Integer get() { return threadLocal.get(); } public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { add(); System.out.println(get()); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { add(); System.out.println(get()); } } }); thread1.start(); thread2.start(); } }
输出结果:
1
2
3
1
2
3
可以看到,每个线程都有 threadLocal 变量的独立副本,各自累加并打印自己的 threadLocal 值,互不影响。这就实现了线程隔离的效果。
如果不使用 ThreadLocal,采用共享变量的方式,像下面这样:
public class NoThreadLocalExample { private static int num; public static void add() { num++; } public static int get() { return num; } public static void main(String[] args) { // ... } }
输出结果会是乱序的,因为两个线程会竞争修改和读取共享变量 num:
1
3
2
5
4
这就是不使用 ThreadLocal 时多个线程之间会相互影响的问题。
ThreadLocal 的注意事项主要有三点:
内存泄漏风险:ThreadLocal 使用弱引用存放数据,如果没有外部强引用,在线程消亡后可能导致内存泄漏。所以使用完 ThreadLocal 后调用 remove() 方法清除数据。
线程安全:ThreadLocal 本身是线程安全的,但存放在 ThreadLocal 中的数据对象不一定是线程安全的。如果多个线程操作同一个数据对象,仍然需要考虑线程同步。
继承性:子线程无法获取父线程设置到 ThreadLocal 中的数据。每个线程都有自己的变量副本。
总结:
ThreadLocal 为每个线程提供了独立的变量副本,实现了线程隔离和避免传参的效果。但是也存在内存泄漏的风险,需要在使用结束后调用 remove() 方法清除数据。并且 ThreadLocal 只为单个线程提供变量副本,子线程无法获取父线程的数据。
ThreadLocal 适用于变量在线程间隔离且在方法调用链上下文传递的场景。它的出现让我们在高并发环境下可以更简单、更优雅地编写代码。
ThreadLocal 的内部实现原理是什么?
ThreadLocal 中使用 ThreadLocalMap 这个内部类来存放每个线程的副本变量。
ThreadLocalMap 使用线程的引用作为 key,变量副本作为 value 存储在一个散列表中。
当调用 ThreadLocal 的 get() 方法时,会先获取当前线程,然后再从这个线程关联的 ThreadLocalMap 中获取变量副本。set() 方法也是类似,会设置当前线程的 ThreadLocalMap 的值。
来看 ThreadLocal 的实现源码:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
我们可以看到:
通过 Thread.currentThread() 获取当前线程
通过 getMap() 方法获取线程的 ThreadLocalMap 对象
如果 ThreadLocalMap 存在,则从中获取或设置线程局部变量的值
如果 ThreadLocalMap 不存在,则创建一个 ThreadLocalMap 对象,并与线程关联后再设置值。
所以,每个线程中都维护着一个 ThreadLocalMap 的引用,这个 ThreadLocalMap 中存储着以 ThreadLocal 为 key 的变量副本,实现了每个线程的变量隔离。
而 ThreadLocal 本身只是作为一个 key 来标识变量,并不持有具体的值,这也是它实现线程隔离的基础。
ThreadLocal 是 Java 中实现线程局部变量的重要机制。它为每个线程创建变量的副本,使每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal 的实现原理是,它使用 ThreadLocalMap 这个内部类给每个线程维护一个变量副本的映射。ThreadLocalMap 使用线程的引用作为 key,变量副本作为 value 存储在一个散列表中。
当通过 ThreadLocal 访问或者设置变量副本时,会首先获取当前线程的引用,然后再通过这个线程去关联的 ThreadLocalMap 中获取或设置对应的值。这样就为每个线程创建了一个独立的变量副本,实现了线程隔离的效果。
ThreadLocal 的主要 API 只有 get()、set() 和 remove() 这三个方法。它简单而强大,理解透彻后会给我们的多线程编程带来许多便利。
ThreadLocal 的应用场景主要有:数据库连接、事务管理、用户上下文传递、避免传参等。它适用于变量在线程之间隔离且需要在方法调用链中传递的场景。
但是 ThreadLocal 的使用也需要注意三点:
内存泄漏:因为 ThreadLocal 使用弱引用,如果没有外部强引用变量副本的话,可能导致内存泄漏。所以使用结束后要调用 remove() 方法删除引用。
线程安全:存放在 ThreadLocal 中的对象必须是线程安全的,否则在多线程环境下会出现问题。
过度使用:滥用 ThreadLocal 会让代码难以理解和维护。
使用 ThreadLocal 实现事务管理:
定义 ThreadLocal 变量来存储 Connection:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
在开始事务时从数据源获取 Connection,并存入 ThreadLocal:
public void beginTransaction() throws SQLException { Connection conn = DataSourceUtils.getConnection(); connectionHolder.set(conn); conn.setAutoCommit(false); }
在事务中使用 get() 方法从 ThreadLocal 获取 Connection:
public void doSomeOperation() { Connection conn = connectionHolder.get(); // 使用 conn 执行 SQL 操作 }
在提交或回滚事务时,从 ThreadLocal 中获取 Connection 并提交或回滚:
public void commitTransaction() throws SQLException { Connection conn = connectionHolder.get(); conn.commit(); conn.close(); connectionHolder.remove(); } public void rollbackTransaction() throws SQLException { Connection conn = connectionHolder.get(); conn.rollback(); conn.close(); connectionHolder.remove(); }
事务结束后调用 remove() 从 ThreadLocal 中移除 Connection。
这样通过 ThreadLocal 存取 Connection,可以实现事务与线程的绑定,并确保每个线程都有自己独立的 Connection 进行事务管理。
这是使用 ThreadLocal 实现简单的事务管理的思路,更 robust 的事务管理框架会涉及更多内容,但 ThreadLocal 仍然是实现线程隔离的关键。