维持线程封闭性的一种更规范方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象
关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有
一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
ThreadLocal对象通常用于防止对可变的单实例变量(singleton)或全局变量进行共享。例如:
在单线程应用程序中可能会位置一个全局的数据库连接,并在程序启动时初始这个连接对象,从而避免
在调用每个方法的时候都要传递一个connection对象。由于jdbc的连接对象不一定是线程安全的,因此,
当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将jdbc的连接保存到
ThreadLocal对象中,每个线程都会拥有属于自己的连接。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection("");
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
}
当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该
临时对象,就可以使用这项技术。例如 在java5.0之前 Integer.toString()方法使用ThreadLocal对象来保存一个
12字节大小的缓冲区,用于对结果进行格式化,而不是使用共享的静态缓冲区或者在每次使用时都分配到一个
新的缓冲区。
当某个线程初次调用ThreadLocal.get方法时,就会调用initialValue获取初始值。从概念上看,你可以将
ThreadLocal<T>视为包含了Map<Thread,T>对象,其中保存了特定该线程的值,但ThreadLocal的实并非如此,
这些特定与线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾回收。
假设你需要将一个单线程应用程序移植到多线程环境中,通过将共享的全局变量转化为ThreadLocal对象可以
位置线程安全性。然后,如果将应用程序范围内的缓存转化为线程局部的缓存,就不会有太大作用。
然后,如果将应用程序范围内的缓存转换为线程局部的缓存,就不会有太大作用。
在实现应用程序框架时大量只用了ThreadLocal。例如,在EJB调用期间,J2EE容器需要将一个事物上下文
(Transcation Context)与某个执行中的线程关联起来。通过将事物上下文保存在静态的ThreadLocal对象中,
可以很容易地实现这个功能;当框架代码需要判断当前运行的是哪一个事物时,只需要从这个ThreadLocal
对象中读取事务上下文。这种机制很方便,因为它避免了在调用每个方法时都要传递上下文信息,然后这也
将使用该机制的代码与框架耦合在一起了。
开发人员经常滥用ThreadLocal,例如将所有全局变量都作为ThreadLocal对象,或者作为一种隐藏方法参数
的手段。ThreadLocal变量类似于全局变量,它能降低代码的可重用性,并在类之间引入隐含的耦合,因此使用
时要格外小心
很多项目中需要在代码中使用当前登录用户的信息,但是又不方便把保存用户信息的session对象传来传去,
这种情况下,就可以考虑使用 ThreadLocal。
ThreadLocal是一个依附于本地线程的变量,按照我的理解,每次对服务器请求,都会使用到一个线程,ThreadLocal的作用就是在这个线程的使用过程中只为这个线程所用。
说说具体如何管理用户session。
现在SSH框架用的比较多,有时在DAO层中需要用到当前用户信息,我们首先定义保存在HttpSession中的用户类,可以是你的用户model,也可以是嵌套了model的包装类,我这里叫UserSession。
在登录方法里将上面定义的用户类对象放入HttpSession中,即:
- session.setAttribute("UserSession", userSession);
写一个用于检验用户的filter,并在web.xml中配置,目的是判断用户有没有登录以及登录用户有没有对要访问链接的权限等,每次请求经过filter时如果HttpSession中的UserSession对象存在的话重新设置一下:
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest)req;
- UserSession userSession = (UserSession)request.getSession().getAttribute("UserSession");
- if (userSession == null) {
- //跳转登录页面
- ...
- } else {
- request.getSession().setAttribute("UserSession", userSession);
- filterChain.doFilter(req, res);
- }
- }
然后创建一个类,用于存放ThreadLocal的静态变量:
- public class SystemSession {
- private static ThreadLocal<UserSession> local = new ThreadLocal<UserSession>();
- public static void setUserSession(UserSession session) {
- local.set(session);
- }
- public static UserSession getUserSession() {
- return local.get();
- }
- }
创建一个监听器,并在web.xml中配置,监听HttpSession的属性变化:
- public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {
- public void attributeAdded(HttpSessionBindingEvent event) {
- if ("UserSession".equals(event.getName())) {
- SystemSession.setUserSession((UserSession)event.getValue());
- }
- }
- public void attributeReplaced(HttpSessionBindingEvent event) {
- if ("UserSession".equals(event.getName())) {
- SystemSession.setUserSession((UserSession)event.getValue());
- }
- }
- }
用户在登录后UserSession对象会放入HttpSession中,触发监听器中的attributeAdded方法,于是UserSession对象同时也会放入ThreadLocal中,登录后每次请求会重新设置UserSession对象,触发监听器的attributeReplaced方法,UserSession对象也会放入ThreadLocal中,在程序中用以下语句即可取得UserSession对象:
- SystemSession.getUserSession();