假设咱们有一个数据库链接管理类:java
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方法都建立新的数据库链接会形成数据库的很大压力,这里能够有两种解决方案:segmentfault
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; } }
内存模型图以下:this
当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清除。url
另外,因为ThreadLocalMap中只对ThreadLocal是弱引用,对value是强引用,若是ThreadLocal由于没有其余强引用而被回收,以后也没有调用过get/set,那么就会产生内存泄露,spa
在使用线程池时,线程会被复用,那么里面保存的ThreadLocalMap一样也会被复用,会形成线程之间的资源没有被隔离,因此在线程归还回线程池时要记得调用remove方法。
上面提到ThreadLocalMap是本身实现的相似HashMap的功能,当出现Hash冲突(经过两个key对象的hash值计算获得同一个数组下标)时,它没有采用链表模式,而是采用的线性探测的方法,既当发生冲突后,就线性查找数组中空闲的位置。当数组较大时,这个性能会不好,因此建议尽可能控制ThreadLocal的数量。