Java多线程之ThreadLocal
ThreadLocal简介
ThreadLocal是Java中的线程局部变量,用于存放线程的局部变量。
ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但是确避免线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
首先看一下ThreadLocal的API:
- get():返回此线程局部变量的当前线程副本中的值。
- protected T initialValue(): 返回此线程局部变量的当前线程的“初始值”。
- void remove(): 移除此线程局部变量当前线程的值。
- void set(T value): 将此线程局部变量的当前线程副本中的值设置为指定值。
可以看出ThreadLocal内部应该就是封装了一个Map,自己实现ThreadLocal:
public class SimpleThreadLocal {
private Map valueMap = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
//①键为线程对象,值为本线程的变量副本
valueMap.put(Thread.currentThread(), newValue);
}
public Object get() {
Thread currentThread = Thread.currentThread();
//②返回本线程对应的变量
Object o = valueMap.get(currentThread);
//③如果在Map中不存在,放到Map中保存起来
if (o == null && !valueMap.containsKey(currentThread)) {
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public void remove() {
valueMap.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}
以上代码很好理解,JDK中的实现比这复杂,可以自行查看源码。
ThreadLocal基本使用
ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。
当一个类中使用了static成员变量的时候,一定要多问问自己,这个static成员变量需要考虑线程安全吗?也就是说,多个线程需要独享自己的static成员变量吗?如果需要考虑,不妨使用ThreadLocal。
例如,在单线程应用程序中可能会维护一个全局的数据库连接,并在程序启动的时候初始化这个连接,从而避免在调用每个方法的时候都要传递一个Connection对象。由于JDBC的连接对象不一定时线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就是不是线程安全的。通过把JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有自己的连接。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
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);
}
}
ThreadLocal需要注意的点
只要使用了“池”(线程池、连接池),在使用ThreadLocal时,尤其需要注意,每个线程在使用ThreadLocal的时候,必须对ThreadLocal执行一次clear操作,避免出现线程污染问题。
线程池中的线程是重复利用的,只要线程还在,ThreadLocal线程本地变量会一直存在系统中,在JavaEE的服务器中尤为明显。
引用高广超在Java解读-ThreadLocal详解与应用中所说:
根据池中的线程数量(在运行环境中大于100个线程是正常的)以及ThreadLocal变量中对象的大小,可能会发生致命的内存问题。例如对线程池中的200个线程进行配置以及将ThreadLocal变量的大小设置为5MB,这将会导致有1GB的堆空间被这些变量所占用。这将会导致一个GC的开销并且可能会由于OutOfMemoryError导致JVM崩溃。
参考资料: