共计 3663 个字符,预计需要花费 10 分钟才能阅读完成。
使用场景
假设我们有一个数据库连接管理类:
class ConnectionManager {
private static Connection connect = null;
private static String url = System.getProperty("URL");
public static Connection openConnection() {if(connect == null){
try {connect = DriverManager.getConnection(url);
} catch (SQLException e) {e.printStackTrace();
}
}
return connect;
}
public static void closeConnection() {if(connect!=null) {
try {connect.close();
} catch (SQLException e) {e.printStackTrace();
}
}
}
}
如果这个类被用在多线程环境内,则会存在线程安全问题,那么可以对这两个方法添加 synchronized 关键字进行同步处理,不过这样会大大降低程序的性能,也可以将 connection 变成局部变量:
class ConnectionManager {
private Connection connect = null;
public Connection openConnection(String url) {if(connect == null){
try {connect = DriverManager.getConnection(url);
} catch (SQLException e) {e.printStackTrace();
}
}
return connect;
}
public void closeConnection() {if(connect!=null) {
try {connect.close();
} catch (SQLException e) {e.printStackTrace();
}
}
}
}
class ConnectionManagerTest {private String url = System.getProperty("URL");
public void insert() {ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection(this.url);
// 使用 connection 进行操作
connectionManager.closeConnection();}
public void update() {ConnectionManager connectionManager = new ConnectionManager();
Connection connection = connectionManager.openConnection(this.url);
// 使用 connection 进行操作
connectionManager.closeConnection();}
}
每个 CURD 方法都创建新的数据库连接会造成数据库的很大压力,这里可以有两种解决方案:
- 使用连接池管理连接,既不是每次都创建、销毁连接,而是从一个连接池里借出可用的连接,用完将其归还。参加 MyBatis 连接管理(1) | MyBatis 连接管理(2)
- 可以看到,这里 connection 的建立最好是这样的:每个线程希望有自己独立的连接来避免同步问题,在线程内部希望共用同一个连接来降低数据库的压力,那么使用 ThreadLocal 来管理数据库连接就是最好的选择了。它为每个线程维护了一个自己的连接,并且可以在线程内共享。
class ConnectionManager {private static String url = System.getProperty("URL");
private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {
try {return DriverManager.getConnection(url);
} catch (SQLException e) {e.printStackTrace();
}
return null;
});
public static Connection openConnection() {return connectionHolder.get();
}
public static void closeConnection() {Connection connect = connectionHolder.get();
if(connect!=null) {
try {connect.close();
} catch (SQLException e) {e.printStackTrace();
}
}
}
}
另外还可以用到其他需要每个线程管理一份自己的资源副本的地方:An Introduction to ThreadLocal in Java
实现原理
这里面涉及到三种对象的映射:Thread-ThreadLocal 对象 -ThreadLocal 中存的具体内容,既然是每个线程都会有一个资源副本,那么这个从 ThreadLocal 对象到存储内容的映射自然就会存在 Thread 对象里:
ThreadLocal.ThreadLocalMap threadLocals = null;
而 ThreadLocal 类只是提供了访问这个 Map 的接口:
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();}
public void set(T value) {Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这个 ThreadLocalMap 是 ThreadLocal 的内部类,实现了一个类似 HashMap 的功能,其内部维护了一个 Entry 数组,下标就是通过 ThreadLocal 对象的 threadLocalHashCode 计算得来。这个 Entry 继承自 WeakReference,实现对 key,也就是 ThreadLocal 的弱引用:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {super(k);
value = v;
}
}
内存模型图如下:
当 ThreadLocal Ref 出栈后,由于 ThreadLocalMap 中 Entry 对 ThreadLocal 只是弱引用,所以 ThreadLocal 对象会被回收,Entry 的 key 会变成 null,然后在每次 get/set/remove ThreadLocalMap 中的值的时候,会自动清理 key 为 null 的 value,这样 value 也能被回收了。
注意:
如果 ThreadLocal Ref 一直没有出栈(例如上面的 connectionHolder,通常我们需要保证 ThreadLocal 为单例且全局可访问,所以设为 static),具有跟 Thread 相同的生命周期,那么这里的虚引用便形同虚设了,所以使用完后记得调用 ThreadLocal.remove 将其对应的 value 清除。
另外,由于 ThreadLocalMap 中只对 ThreadLocal 是弱引用,对 value 是强引用,如果 ThreadLocal 因为没有其他强引用而被回收,之后也没有调用过 get/set,那么就会产生内存泄露,
在使用线程池时,线程会被复用,那么里面保存的 ThreadLocalMap 同样也会被复用,会造成线程之间的资源没有被隔离,所以在线程归还回线程池时要记得调用 remove 方法。
hash 冲突
上面提到 ThreadLocalMap 是自己实现的类似 HashMap 的功能,当出现 Hash 冲突(通过两个 key 对象的 hash 值计算得到同一个数组下标)时,它没有采用链表模式,而是采用的线性探测的方法,既当发生冲突后,就线性查找数组中空闲的位置。当数组较大时,这个性能会很差,所以建议尽量控制 ThreadLocal 的数量。