简介
-
线程本地变量,或线程本地存储,可以为变量在每个线程中都创建一个副本,使每个线程都可以访问自己内部的副本变量。
-
举个例子:
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(); } }
-
有这样一个数据库连接管理类,它在单线程中使用没有任何问题,但是如果在多线程中使用呢?
-
显然,在多线程中使用会存在线程安全问题。
-
第一,两个方法都没有进行同步,很可能在openConnection()中会多次创建connect。
-
第二,由于connect是共享变量,那么在调用connect的地方必然需要使用同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作时,另一个线程会调用closeConnection()关闭连接。
-
故,必须将两个方法进行同步处理,并且在调用connect的地方也进行同步。
-
而这样将会大大影响程序执行效率,因为一个线程在使用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还要大。
源码
-
原理:每个Thread内部有一个副本集合ThreadLocalMap对象threadLocals,其键为当前ThreadLocal对象,值为变量副本。初始调用get()/set()时,就会对Thread类中的threadLocals集合初始化;然后如果要使用副本变量,就可以通过get()在threadLocals里查找。
-
public T get():获取当前线程中保存的副本。
-
public void set(T value):设置当前线程中某个变量的副本。
-
public void remove():移除副本;调用set()之前要先移除,保证副本集合内只有一个键值对。
-
protected T initialValue():一般是用来在使用时进行重写的,是一个延迟加载的方法。
-
首先看get():
public T get() { //获得当前线程 Thread t = Thread.currentThread(); //以该线程为键,获取其值,即为之前保存的变量副本 ThreadLocalMap map = getMap(t); if (map != null) { //若集合不为空,则根据键获得对应的值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } //若集合为空,则创建一个集合 return setInitialValue(); }
-
getMap():
ThreadLocalMap getMap(Thread t) { //返回副本集合 return t.threadLocals; }
-
ThreadLocalMap类:副本集合,一个线程可有多个ThreadLocal对象,每个ThreadLocal对象对应一个变量副本。
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; //使用ThreadLocal作为键,value为值 Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
-
setInitialValue():
private T setInitialValue() { //未重写情况下返回null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //如果map不为空,就设置键值对 if (map != null) map.set(this, value); //为空,则创建副本集合ThreadLocalMap else createMap(t, value); return value; }
-
因此,get()之前必须进行过set(),否则返回的value将为null,除非重写initialValue()返回一个默认值作为value。
-
createMap():
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
-
测试:
public class Test { ThreadLocal<Long> longLocal = new ThreadLocal<Long>(); ThreadLocal<String> stringLocal = new ThreadLocal<String>(); public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final Test test = new Test(); test.set(); System.out.println(test.getLong()); //1 System.out.println(test.getString()); //main //开新线程 Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); //8 System.out.println(test.getString()); //Thread-0 }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); //1 System.out.println(test.getString()); //main } }
-
测试结果:
应用
-
最常用于解决数据库连接、Session管理等。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection() { return connectionHolder.get(); }
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}