ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。
这个玩意有什么用处,或者说为什么要有这么一个东东?先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。
我们从源码的角度来分析这个问题。
首先定义一个ThreadLocal:
1public class ConnectionUtil { 2 private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); 3 private static Connection initConn = null; 4 static { 5 try { 6 initConn = DriverManager.getConnection("url, name and password"); 7 } catch (SQLException e) { 8 e.printStackTrace(); 9 } 10 } 11 12 public Connection getConn() { 13 Connection c = tl.get(); 14 if(null == c) tl.set(initConn); 15 return tl.get(); 16 } 17 18}
这样子,都是用同一个连接,但是每个连接都是新的,是同一个连接的副本。
那么实现机制是如何的呢?
1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。
1/* ThreadLocal values pertaining to this thread. This map is maintained 2 * by the ThreadLocal class. */ 3ThreadLocal.ThreadLocalMap threadLocals = null;
2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
1public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) 7 return (T)e.value; 8 } 9 return setInitialValue(); 10}
3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。 2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
4、总结:当我们调用get方法的时候,其实每个当前线程中都有一个ThreadLocal。每次获取或者设置都是对该ThreadLocal进行的操作,是与其他线程分开的。
5、应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。
6、其实说再多也不如看一下源码来得清晰。如果要看源码,其中涉及到一个WeakReference和一个Map,这两个地方需要了解下,这两个东西分别是a.Java的弱引用,也就是GC的时候会销毁该引用所包裹(引用)的对象,这个threadLocal作为key可能被销毁,但是只要我们定义成他的类不卸载,tl这个强引用就始终引用着这个ThreadLocal的,永远不会被gc掉。b.和HashMap差不多。
事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。
举个栗子:
接口定义:
1public interface DataBind<T> { 2 void put(T t); 3 4 T get(); 5 6 void remove(); 7}
继承父类:
1public class ThreadLocalDataBind<T> implements DataBind<T> { 2 private final ThreadLocal<T> threadLocal = new ThreadLocal<T>(); 3 4 @Override 5 public void put(T t) { 6 threadLocal.set(t); 7 } 8 9 @Override 10 public T get() { 11 return threadLocal.get(); 12 } 13 14 @Override 15 public void remove() { 16 threadLocal.remove(); 17 } 18}
单例定义:
1public final class DataBindManager { 2 private static final DataBindManager INSTANCE = new DataBindManager(); 3 private final ConcurrentHashMap<Enum, DataBind> map = new ConcurrentHashMap<Enum, DataBind>(); 4 5 private DataBindManager() { 6 } 7 8 public static DataBindManager getInstance() { 9 return INSTANCE; 10 } 11 12 public <T> DataBind<T> getDataBind(Enum bindType) { 13 DataBind<T> dataBind = map.get(bindType); 14 if (dataBind == null) { 15 DataBind bind = new ThreadLocalDataBind(); 16 dataBind = map.putIfAbsent(bindType, bind); 17 if (dataBind == null) { 18 dataBind = bind; 19 } 20 } 21 return dataBind; 22 } 23 24 25 public enum DataBindType { 26 WEI_XIN_OPEN_ID, //微信账号 27 UNION_ID, //公众平台id 28 USER_ID, //用户uid 29 REFER_UID, 30 USER_INFO, //用户信息 31 WEI_XIN_PAY_OPEN_ID, //微信支付账号 32 } 33}
在单例中的局部变量使用,本例子中在拦截器中使用(spring mvc中controller也为ioc管理的singleton单例):
1public class AutoLoginInterceptor extends AbstractDetectingUrlInterceptor { 2 private final static Logger log = LoggerFactory.getLogger(AutoLoginInterceptor.class); 3 private static final DataBind<Long> USER_ID_BIND = DataBindManager.getInstance().getDataBind(DataBindManager.DataBindType.USER_ID); 4 private static final DataBind<UserTo> USER_INFO_BIND = DataBindManager.getInstance().getDataBind(DataBindManager.DataBindType.USER_INFO); 5 @Autowired 6 private WmpUserService wmpUserService; 7 @Autowired 8 private Contants contants; 9 10 @Override 11 protected boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 12 //原有uid 13 Long uid = USER_ID_BIND.get(); 14 15 String url = request.getRequestURI(); 16 String e_m = request.getParameter("e_m"); 17 //短连接手机号自动登录 18 if (StringUtils.isNotEmpty(e_m) && !isWxBrower(request) && !isAppBrower(request)) { 19 String decryptMob = MobileHelper.getDecryptMobile(e_m); 20 if (StringUtils.isEmpty(decryptMob)) { 21 log.error("encryptMobile incorrect! deCode = {},{}", decryptMob, url); 22 response.sendRedirect("/wmp/tool/error"); 23 return false; 24 } 25 String[] arr = decryptMob.split("-"); 26 UserTo userTo = wmpUserService.isRegisterUser(arr[0], arr[1]); 27 if (userTo != null) { 28 Long userId = userTo.getUserId(); 29 if (userId != null && userId > 0) { 30 //覆盖掉原有登录id 31 USER_ID_BIND.put(userId); 32 USER_INFO_BIND.put(userTo); 33 CookieUtil.writeMobileUidCookie(response, "" + userId, contants.mobileHost); 34 log.info("using encryptMobile ! deCode = {},old={},new={},{}", decryptMob, uid, userId, url); 35 } 36 } else { 37 log.error("get userTo is null! deCode = {}", decryptMob); 38 } 39 } 40 return true; 41 } 42 43 protected boolean isWxBrower(HttpServletRequest request) { 44 Object object = request.getAttribute(CommonContants.KEY_IS_WXBROWER); 45 return object != null && object instanceof Boolean && (Boolean) object; 46 } 47 48 protected boolean isAppBrower(HttpServletRequest request) { 49 Object object = request.getAttribute(CommonContants.KEY_IS_APP); 50 return object != null && object instanceof Boolean && (Boolean) object; 51 } 52}