现在的位置: 首页 > 综合 > 正文

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

2013年09月25日 ⁄ 综合 ⁄ 共 4295字 ⁄ 字号 评论关闭

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

 

 

抱歉!评论已关闭.