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

from http://blog.csdn.net/drifterj/article/details/7782706

ThreadLocal是Java从1.2版本就开始提供的一个类,顾名思义,就是线程级别的本地变量。目前在两种情况下采用了ThreadLocal类,以下分别进行介绍:

1》 为多线程并发的互斥控制提供了另一种全新的解决思路。前面提到多线程对同一个资源进行访问的互斥是通过关键字synchronized进行的。但使用这个关键字有一个副作用,那就是性能的损耗并且会遏制虚拟机对字节码的优化处理。我们来看一个例子:DateFormat类是Java提供的日期格式化类,我们使用这个类可以这样做:

  1. public String format(Date date){  
  2.               
  3.     DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  4.     return formatter.format(date);  
  5. }  


这样写不会有多线程并发的问题,但因为DateFormat对象的创建是一个耗时操作,我们应该避免将其做个局部变量,否则大规模调用这个方法会出现性能瓶颈。但如果做成对象级或类级成员变量,又不可避免的出现多线程并发的问题,根据JDK文档,DateFormat不支持多线程并发,所以我们必须这样处理:

  1. private static DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  2.           
  3. public synchronized String format(Date date){  
  4.               
  5.     return formatter.format(date);  
  6. }  

这样做还是没有问题,但synchronized关键字还是会影响性能。这个时候通过ThreadLocal就可以有一个两全其美的解决方案:为每一个线程分配一个DateFormat对象,这样不会创建大量的DateFormat对象,同时也不会有并发问题,代码如下:

  1. /** 
  2.      * DateFormat对象生产工厂。因为DateFormat创建成本较高,并且还是非线程安全的,所以通过ThreadLocal去实现DateFormat对象的存储。 
  3.      * 即一个线程一个DateFormat对象。 
  4.      */  
  5.     protected static class DateFormatFactory{  
  6.           
  7.         /** 
  8.          * key为格式串,如"yyyy-MM-dd HH:mm:ss" 
  9.          */  
  10.         private static final Map<String, ThreadLocal<DateFormat>> PATTERN_2_DF = new HashMap<String, ThreadLocal<DateFormat>>();  
  11.         private static final Object LOCK = new Object();  
  12.         private static final String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";  
  13.           
  14.         private DateFormatFactory() {  
  15.               
  16.         }  
  17.           
  18.         public static DateFormat getDateFormatter(String pattern){  
  19.               
  20.             ThreadLocal<DateFormat> tl = PATTERN_2_DF.get(pattern);  
  21.             if(null == tl || null == tl.get()){  
  22.                   
  23.                 synchronized(LOCK){  
  24.                       
  25.                     tl = PATTERN_2_DF.get(pattern);  
  26.                     if(null == tl){  
  27.                           
  28.                         tl = new ThreadLocal<DateFormat>();  
  29.                     }  
  30.                     DateFormat df = tl.get();  
  31.                     if(null == df){  
  32.                           
  33.                         try{  
  34.                             df = new SimpleDateFormat(pattern);  
  35.                         }catch(Exception e){  
  36.                               
  37.                             df = new SimpleDateFormat(DEFAULT_FORMAT);  
  38.                         }  
  39.                         tl.set(df);  
  40.                     }  
  41.                     PATTERN_2_DF.put(pattern, tl);  
  42.                 }  
  43.             }  
  44.               
  45.             return PATTERN_2_DF.get(pattern).get();  
  46.               
  47.         }  
  48.     }  



通过上述基于ThreadLocal写的DateFormat工厂类,可以实现每一个线程一个DateFormat对象,这样不会大量创建该对象,并且不会出现并发操作的问题。

2》 通过ThreadLocal为其他模块的API传递参数。我们在实际编码中经常需要调用其他模块的功能,有时我们可能需要继承其他模块的类覆写某个方法。但在覆写某个方法时会发现该方法的参数不足,不允许我们进行足够的分支处理。上例子吧:

  1. package cn.test;  
  2.   
  3. public class BusiProcesser {  
  4.       
  5.     public void processOrder(String orderId, double price){  
  6.           
  7.         processA(orderId, price);  
  8.         processB(orderId, price);  
  9.     }  
  10.       
  11.     public void processA(String orderId, double price){  
  12.           
  13.         // 业务处理逻辑  
  14.     }  
  15.       
  16.     public void processB(String orderId, double price){  
  17.           
  18.         // 业务处理逻辑  
  19.     }  
  20.   
  21. }  



这是某系统订单模块提供的一个订单处理功能,我们在使用中发现它不足以处理我们目前的功能,在确认该类不能修改后,我们决定继承这个类实现自己的订单处理类,如下:

 

  1. package cn.test;  
  2.   
  3. public class MyBusinessProcesser {  
  4.       
  5.     public void processOrder(String orderId, double price){  
  6.           
  7.         /** 
  8.          我们这里需要根据当前用户的类别进行一下分支,但痛苦的是,我们没法获得当前用户类别, 
  9.         原始方法中没有当前用户类别的参数,并且根据其他参数也无法获取这个!!这个该怎么办? 
  10.          
  11.         if(当前用户类别 == "大客户"){ 
  12.              
  13.             processA(orderId, price); 
  14.         }else{ 
  15.              
  16.             processC(orderId, price); 
  17.         }*/  
  18.         processB(orderId, price);  
  19.     }  
  20.       
  21.     public void processA(String orderId, double price){  
  22.           
  23.         // 业务处理逻辑  
  24.     }  
  25.       
  26.     public void processB(String orderId, double price){  
  27.           
  28.         // 业务处理逻辑  
  29.     }  
  30.       
  31.     /** 
  32.      * 我们自己独特逻辑处理方法 
  33.      * @param orderId 
  34.      * @param price 
  35.      */  
  36.     public void processC(String orderId, double price){  
  37.           
  38.         // 业务处理逻辑  
  39.     }  
  40.   
  41. }  

从代码中,我们看到了难点在哪里,那就是我们在覆写方法时,可能需要一些参数(如上例的当前下订单的用户)进行分支处理,但原始方法中却没有提供这个参数,我们可以通过ThreadLocal 在调用这个方法处将这个值以线程级变量的形式进行存储,然后在该方法中从ThreadLocal中得到这个变量即可,代码如下:

  1. package cn.test;  
  2.   
  3.   
  4. public class MyBusinessProcesser {  
  5.       
  6.     public void processOrder(String orderId, double price){  
  7.           
  8.         /** 
  9.          我们这里需要根据当前用户的类别进行一下分支,但痛苦的是,我们没法获得当前用户类别, 
  10.         原始方法中没有当前用户类别的参数,并且根据其他参数也无法获取这个!!这个该怎么办? 
  11.          
  12.         */  
  13.         MyThreadLocal mtl = MyThreadLocal.getInstance();  
  14.         String currentUser = String.valueOf(mtl.getThreadValue("currentUser"));  
  15.         if("大客户".equals(currentUser)){  
  16.               
  17.             processA(orderId, price);  
  18.         }else{  
  19.               
  20.             processC(orderId, price);  
  21.         }  
  22.         processB(orderId, price);  
  23.     }  
  24.       
  25.     public void processA(String orderId, double price){  
  26.           
  27.         // 业务处理逻辑  
  28.     }  
  29.       
  30.     public void processB(String orderId, double price){  
  31.           
  32.         // 业务处理逻辑  
  33.     }  
  34.       
  35.     /** 
  36.      * 我们自己独特逻辑处理方法 
  37.      * @param orderId 
  38.      * @param price 
  39.      */  
  40.     public void processC(String orderId, double price){  
  41.           
  42.         // 业务处理逻辑  
  43.     }  
  44.       
  45.     /** 
  46.      * 订单业务处理调用处示例 
  47.      */  
  48.     public static void main(String[] args) {  
  49.           
  50.         MyThreadLocal mtl = MyThreadLocal.getInstance();  
  51.         mtl.setThreadValue("currentUser""大客户");  
  52.         MyBusinessProcesser processer = new MyBusinessProcesser();  
  53.         processer.processOrder("1000101"100);  
  54.     }  
  55.   
  56. }  


MyThreadLocal类的代码如下:

  1. package cn.test;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. public class MyThreadLocal {  
  7.       
  8.     private final static MyThreadLocal INSTANCE = new MyThreadLocal();  
  9.     private ThreadLocal<Map<String, Object>>  tl = new ThreadLocal<Map<String, Object>>();  
  10.     private Object lock = new Object();  
  11.       
  12.     private MyThreadLocal(){  
  13.           
  14.     }  
  15.       
  16.     public static MyThreadLocal getInstance(){  
  17.           
  18.         return INSTANCE;  
  19.     }  
  20.       
  21.     public void setThreadValue(String key, String value){  
  22.           
  23.         if(null == tl.get()){  
  24.               
  25.             synchronized(lock){  
  26.                   
  27.                 tl.set(new HashMap<String, Object>());  
  28.             }  
  29.         }  
  30.         tl.get().put(key, value);  
  31.     }  
  32.       
  33.     public Object getThreadValue(String key){  
  34.           
  35.         if(null == tl.get()){  
  36.               
  37.             return null;  
  38.         }  
  39.         return tl.get().get(key);  
  40.     }  
  41. }  

通过ThreadLocal类,我们就可以将一些所需变量通过线程带到相应的方法调用中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值