Java多线程编程--(4)ThreadLocal的使用

来源:互联网 发布:会议录音软件 编辑:程序博客网 时间:2024/06/07 13:24

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类,我们就可以将一些所需变量通过线程带到相应的方法调用中。


 

 

原创粉丝点击