使用场景
假设我们有一个数据库连接管理类:
- 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 方法都创建新的数据库连接会造成数据库的很大压力, 这里可以有两种解决方案:
使用连接池管理连接, 既不是每次都创建, 销毁连接, 而是从一个连接池里借出可用的连接, 用完将其归还.
可以看到, 这里 connection 的建立最好是这样的: 每个线程希望有自己独立的连接来避免同步问题, 在线程内部希望共用同一个连接来降低数据库的压力, 那么使用 ThreadLocal 来管理数据库连接就是最好的选择了. 它为每个线程维护了一个自己的连接, 并且可以在线程内共享.
- class ConnectionManager {
- private static String url = System.getProperty("URL");
- private static ThreadLocalconnectionHolder = 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
内存模型图如下:
当 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 的数量.
来源: http://www.jianshu.com/p/3bf4be56cf19