ThreadLocal
ThreadLocal 是 Java 中一个非常有用的类,它允许你创建线程局部变量。线程局部变量是指每个线程都有自己独立的变量副本,互不干扰。ThreadLocal 主要用于解决多线程环境下共享数据的线程安全性问题。
基本用法
创建 ThreadLocal 变量
ThreadLocal<Integer> threadLocalVariable = new ThreadLocal<>();
设置和获取线程局部变量:
threadLocalVariable.set(42); // 设置线程局部变量的值
int value = threadLocalVariable.get(); // 获取线程局部变量的值
初始值:
你可以通过覆盖 ThreadLocal 的 initialValue 方法来指定线程局部变量的初始值。例如:
ThreadLocal<Integer> threadLocalVariable = ThreadLocal.withInitial(() -> 0);
注意事项:
使用 ThreadLocal 时要小心内存泄漏,确保在不需要使用线程局部变量时及时清理。
在使用线程池时,注意线程复用可能导致线程局部变量的状态被共享。
ThreadLocal 是一个有助于在多线程应用程序中维护线程局部状态的重要工具,但它需要谨慎使用,以避免潜在的问题。确保理解其工作原理,并在需要的情况下适当使用它,可以提高多线程程序的性能和可维护性。
ThreadLocal理解
ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。#
最多的是session管理和数据库链接管理,这里以数据访问为例帮助你理解ThreadLocal:
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if (connect == null) {
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if (connect != null)
connect.close();
}
}
据库管理类在单线程使用是没有任何问题的,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,多次进行closeConnection可能会导致空指针异常问题;第三,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。
为了解决上述线程安全的问题,第一考虑:互斥同步
你可能会说,将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理,比如用Synchronized或者ReentrantLock互斥锁。
这里再抛出一个问题:这地方到底需不需要将connect变量进行共享?
事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。即改后的代码可以这样:
class ConnectionManager {
private Connection connect = null;
public Connection openConnection() {
if (connect == null) {
connect = DriverManager.getConnection();
}
return connect;
}
public void closeConnection() {
if (connect != null)
connect.close();
}
}
class Dao {
public void insert() {
ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection();
// 使用connection进行操作
connectionManager.closeConnection();
}
}
这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不仅严重影响程序执行效率,还可能导致服务器压力巨大。
这时候ThreadLocal登场了那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。下面就是网上出现最多的例子:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionManager {
private static final ThreadLocal<Connection> dbConnectionLocal = new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection("", "", "");
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
};
public Connection getConnection() {
return dbConnectionLocal.get();
}
}
ThreadLocal原理
如何实现线程隔离
主要是用到了Thread对象中的一个ThreadLocalMap类型的变量threadLocals, 负责存储当前线程的关于Connection的对象, dbConnectionLocal(以上述例子中为例) 这个变量为Key, 以新建的Connection对象为Value; 这样的话, 线程第一次读取的时候如果不存在就会调用ThreadLocal的initialValue方法创建一个Connection对象并且返回;
具体关于为线程分配变量副本的代码如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap threadLocals = getMap(t);
if (threadLocals != null) {
ThreadLocalMap.Entry e = threadLocals.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html