ThreadLocal是Java从1.2版本就开始提供的一个类,顾名思义,就是线程级别的本地变量。目前在两种情况下采用了ThreadLocal类,以下分别进行介绍:
1》 为多线程并发的互斥控制提供了另一种全新的解决思路。前面提到多线程对同一个资源进行访问的互斥是通过关键字synchronized进行的。但使用这个关键字有一个副作用,那就是性能的损耗并且会遏制虚拟机对字节码的优化处理。我们来看一个例子:DateFormat类是Java提供的日期格式化类,我们使用这个类可以这样做:
public String format(Date date){ DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return formatter.format(date); }
这样写不会有多线程并发的问题,但因为DateFormat对象的创建是一个耗时操作,我们应该避免将其做个局部变量,否则大规模调用这个方法会出现性能瓶颈。但如果做成对象级或类级成员变量,又不可避免的出现多线程并发的问题,根据JDK文档,DateFormat不支持多线程并发,所以我们必须这样处理:
private static DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public synchronized String format(Date date){ return formatter.format(date); }
这样做还是没有问题,但synchronized关键字还是会影响性能。这个时候通过ThreadLocal就可以有一个两全其美的解决方案:为每一个线程分配一个DateFormat对象,这样不会创建大量的DateFormat对象,同时也不会有并发问题,代码如下:
/** * DateFormat对象生产工厂。因为DateFormat创建成本较高,并且还是非线程安全的,所以通过ThreadLocal去实现DateFormat对象的存储。 * 即一个线程一个DateFormat对象。 */ protected static class DateFormatFactory{ /** * key为格式串,如"yyyy-MM-dd HH:mm:ss" */ private static final Map<String, ThreadLocal<DateFormat>> PATTERN_2_DF = new HashMap<String, ThreadLocal<DateFormat>>(); private static final Object LOCK = new Object(); private static final String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss"; private DateFormatFactory() { } public static DateFormat getDateFormatter(String pattern){ ThreadLocal<DateFormat> tl = PATTERN_2_DF.get(pattern); if(null == tl || null == tl.get()){ synchronized(LOCK){ tl = PATTERN_2_DF.get(pattern); if(null == tl){ tl = new ThreadLocal<DateFormat>(); } DateFormat df = tl.get(); if(null == df){ try{ df = new SimpleDateFormat(pattern); }catch(Exception e){ df = new SimpleDateFormat(DEFAULT_FORMAT); } tl.set(df); } PATTERN_2_DF.put(pattern, tl); } } return PATTERN_2_DF.get(pattern).get(); } }
通过上述基于ThreadLocal写的DateFormat工厂类,可以实现每一个线程一个DateFormat对象,这样不会大量创建该对象,并且不会出现并发操作的问题。
2》 通过ThreadLocal为其他模块的API传递参数。我们在实际编码中经常需要调用其他模块的功能,有时我们可能需要继承其他模块的类覆写某个方法。但在覆写某个方法时会发现该方法的参数不足,不允许我们进行足够的分支处理。上例子吧:
package cn.test; public class BusiProcesser { public void processOrder(String orderId, double price){ processA(orderId, price); processB(orderId, price); } public void processA(String orderId, double price){ // 业务处理逻辑 } public void processB(String orderId, double price){ // 业务处理逻辑 } }
这是某系统订单模块提供的一个订单处理功能,我们在使用中发现它不足以处理我们目前的功能,在确认该类不能修改后,我们决定继承这个类实现自己的订单处理类,如下:
package cn.test; public class MyBusinessProcesser { public void processOrder(String orderId, double price){ /** 我们这里需要根据当前用户的类别进行一下分支,但痛苦的是,我们没法获得当前用户类别, 原始方法中没有当前用户类别的参数,并且根据其他参数也无法获取这个!!这个该怎么办? if(当前用户类别 == "大客户"){ processA(orderId, price); }else{ processC(orderId, price); }*/ processB(orderId, price); } public void processA(String orderId, double price){ // 业务处理逻辑 } public void processB(String orderId, double price){ // 业务处理逻辑 } /** * 我们自己独特逻辑处理方法 * @param orderId * @param price */ public void processC(String orderId, double price){ // 业务处理逻辑 } }
从代码中,我们看到了难点在哪里,那就是我们在覆写方法时,可能需要一些参数(如上例的当前下订单的用户)进行分支处理,但原始方法中却没有提供这个参数,我们可以通过ThreadLocal 在调用这个方法处将这个值以线程级变量的形式进行存储,然后在该方法中从ThreadLocal中得到这个变量即可,代码如下:
package cn.test; public class MyBusinessProcesser { public void processOrder(String orderId, double price){ /** 我们这里需要根据当前用户的类别进行一下分支,但痛苦的是,我们没法获得当前用户类别, 原始方法中没有当前用户类别的参数,并且根据其他参数也无法获取这个!!这个该怎么办? */ MyThreadLocal mtl = MyThreadLocal.getInstance(); String currentUser = String.valueOf(mtl.getThreadValue("currentUser")); if("大客户".equals(currentUser)){ processA(orderId, price); }else{ processC(orderId, price); } processB(orderId, price); } public void processA(String orderId, double price){ // 业务处理逻辑 } public void processB(String orderId, double price){ // 业务处理逻辑 } /** * 我们自己独特逻辑处理方法 * @param orderId * @param price */ public void processC(String orderId, double price){ // 业务处理逻辑 } /** * 订单业务处理调用处示例 */ public static void main(String[] args) { MyThreadLocal mtl = MyThreadLocal.getInstance(); mtl.setThreadValue("currentUser", "大客户"); MyBusinessProcesser processer = new MyBusinessProcesser(); processer.processOrder("1000101", 100); } }
MyThreadLocal类的代码如下:
package cn.test; import java.util.HashMap; import java.util.Map; public class MyThreadLocal { private final static MyThreadLocal INSTANCE = new MyThreadLocal(); private ThreadLocal<Map<String, Object>> tl = new ThreadLocal<Map<String, Object>>(); private Object lock = new Object(); private MyThreadLocal(){ } public static MyThreadLocal getInstance(){ return INSTANCE; } public void setThreadValue(String key, String value){ if(null == tl.get()){ synchronized(lock){ tl.set(new HashMap<String, Object>()); } } tl.get().put(key, value); } public Object getThreadValue(String key){ if(null == tl.get()){ return null; } return tl.get().get(key); } }
通过ThreadLocal类,我们就可以将一些所需变量通过线程带到相应的方法调用中。