Java基础知识系列:线程变量

问题场景一:

      Web应用中,后台一般都分成几层,最常用的分法有控制层、业务逻辑层、数据持久层和表现层。一般情况下,我们都会把当前用户存放在HttpSession里面。怎么获取到当前用户的信息呢?首先,要获得HttpServletRequest对象,然后通过它的getSession()方法获取HttpSession对象,从而根据对应的Key值拿到用户信息。

      现在我们提出一个新的要求,在控制层、业务逻辑层、数据持久层和表现层都能方便的获取到当前用户的信息。怎么实现呢?

      想一想,发现了两种方法:

(1)控制层肯定可以拿到HttpServletRequest对象,所以控制层是可以获取到用户信息的,那么业务逻辑层、数据持久层和表现层怎么办呢?最直接的方法就是在上层调用下层的时候通过方法参数传下去。这样各个层的都能获得HttpServletRequest对象。那么就可以实现各层获取当前用户的信息了。

(2)新建一个类,类里面有一个静态Map,一开始的时候,将当前HttpServletRequest保存到这个类的Map中,问题是放进去后,我们凭什么拿出来呢?Map的key值放什么好呢?必须放一个到处都可以拿到的对象,并且可以表示我当前这个请求。我们想到了当前线程,Servlet容器对于每个请求都有一个线程来处理的,所以一个请求处理时间内,一个线程可以唯一标识一个请求。那么,我们就那当前线程对象作为Map的key值,value值为HttpServletRequest对象。这样我们再通过这个类的一个静态方法,传入当前线程对象从而获得HttpServletRequest对象,从而获取到当前用户的信息。

问题场景二:

      一个请求的处理涉及几次数据库操作,假如这几次数据库操作都是针对同一个数据库用户的。正常情况下,JDBC执行的顺序是:

(1)获取一个数据库连接

(2)数据库操作1

(3)数据库操作2

(4)组装结果数据

(5)是释放连接回连接池

但是,很多封装好的持久层框架(比如Spring、Ibatis等)的执行顺序是这样的:

(1)获取一个数据库连接

(2)数据库操作1

(3)组装结果数据

(4)是释放连接回连接池

(5)获取一个数据库连接

(6)数据库操作2

(7)组装结果数据

(8)是释放连接回连接池

就第二种情况,两次的数据库操作是分开的,怎么保证两次数据库操作能在同一个事务里面,我们知道同一个事务的前提是两次操作的数据库连接要是同一个才行。所以怎么保证,一次请求的处理过程中,都在同一个事务中。说白了,就是怎么保证一次请求处理的过程中,拿到的数据库连接永远是同一个。

     这个时候,我们就回想到,能不能跟场景一一样,让连接跟线程绑定,每次获取到的数据库连接都会是同一个。

线程变量实现

(1)自己动手实现:

public class ThreadVariable {
	private static Map variableMap = new HashMap();
	
	public static void addVariable(String variableName,String variableValue){
		Map map = null;
		Long threadId = Thread.currentThread().getId();
		if(variableMap.containsKey(threadId)){
			map = (Map)variableMap.get(threadId);
		}
		else{
			map = new HashMap();
			variableMap.put(threadId, map);
		}
		
		map.put(variableName, variableValue);
	}
	
	public static Object getVariable(String variableName) throws Exception{
		Long threadId = Thread.currentThread().getId();
		if(variableMap.containsKey(threadId)){
			Map map = (Map)variableMap.get(threadId);
			return map.get(variableName);
		}
		else{
			throw new Exception("the variable [" + variableName + "] does not exist");
		}
	}
	
	public static boolean removeVariable(String variableName){
		try{
			variableMap.remove(variableName);
			return true;
		}catch(Exception e){
			return false;
		}
	}

}

(2)使用JDK本身支持的ThreadLocal对象

private static final ThreadLocal context = new ThreadLocal();

context.set("zhangsan");

Object object = context.get();
 

使用总结:

(1)解决多线程并发的资源共享问题,每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

注意:并不是所有使用同步的地方都可以改用线程变量的方式,因为有些数据是必须要保持全局的唯一、一致性的,这种情况下如果使用线程变量则会有多份不一致的副本。

(2)保证一个线程所涉及的各个层次的代码都可能访问到同一个线程的变量。

比如:怎么保证一次线程处理的数据库操作能够统一提交?事务的统一提交是有前提的,都使用同一个Connection对象。

对于多线程资源共享的问题,我们比较一下同步机制和线程变量的区别:
(1)同步机制采用了“以时间换空间”的方式:访问串行化,数据共享化。多个线程同时访问同一个资源需要排队等候。
(2)线程变量采用了“以空间换时间”的方式:访问并行化,为每一个线程都提供了一份变量。

相关推荐