ThreadLocal很多地方叫做线程本地变量,也有线程本地存储的叫法,它为变量在每个线程中创建一个副本,那么每个线程可以访问自己内部的副本变量
ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制
比如数据库连接的例子:
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(); } }
上面的代码在多线程中使用有线程安全问题,因为connect是共享变量,而两个方法使用共享变量的方法都没有进行同步.
如果两个方法都进行同步处理,而且在在调用connect的地方进行了同步处理确实可以解决这个问题,但是这样会大大影响程序执行的效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待.
那么事实上,这里是不需要将connect变量共享的,因为一个线程不关心其他线程是否对这个connect进行了修改.
那么既然不需要共享,就可以不用static的,用实例的方式在每个需要使用数据库连接的地方调用,使用后再释放这个连接,如下
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在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个 该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
ThreadLocal 类提供的几个方法:
public T get() { } public void set(T value) { } public void remove() { } protected T initialValue() { }
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,
set()用来设置当前线程中变量的副本,
remove()用来移除当前 线程中变量的副本,
initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。
ThreadLocal为每个线程创建变量的副本的方法:
(1) 首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个 threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
(2) 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对 Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为 value,存到threadLocals。
(3) 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
下面简单的例子说明通过ThreadLocal能达到在每个线程中创建变量副本的效果
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(); //anchor System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); } }
输出结果:
1 main 9 Thread-0 1 main
从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。
总结一下:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i, 而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。
看上面的anchor,也就是test.set(),不写会报NullPointerException,而用下面的写法不写set方法也可
ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){ protected Long initialValue() { return Thread.currentThread().getId(); } }; ThreadLocal<String> stringLocal = new ThreadLocal<String>(){ protected String initialValue() { return Thread.currentThread().getName(); } };
应用场景:
最常见的使用场景是用来解决数据库连接,session管理等
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection() { return connectionHolder.get(); }
下面来看一个hibernate中典型的ThreadLocal的应用
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; }