Struts2中的设计模式ThreadLocal模式续

                              本文主要整理自----《Struts2技术内幕》      陆舟 著

  ThreadLocal模式的应用场景

  

  在分析了ThreadLocal的源码之后,我们来看看ThreadLocal模式最合适的业务场景。在一个完整的“请求-响应”过程中,主线程的执行过程总是贯穿始终。当这个主线程的执行过程中被加入了ThreadLocal的读写时,会对整个过程产生怎样的影响呢?我们根据之前源码分析的结果,并结合 分层开发模式,把整个流程画下来,如图4-1所示:

                                              

  从上面图中我们可以看到,由于ThreadLocal所操作的是维持于整个Thread生命周期的副本(ThreadLocalMap),所以无论在 J2EE程序的哪个层次(表示层、业务逻辑层或者持久层),只要在一个Thread的生命周期之内,存储于ThreadLocalMap中的对象都是 线程安全的(因为ThreadLocalMap本身仅仅隶属于当前的执行线程,是执行线程内部的一个属性变量。我们用图中的阴影部分来表示这个变量的存储空间)。而这一点,正是被我们用于来解决多线程环境中的变量共享问题的核心技术。ThreadLocal的这一特性也使其能够被广泛地应用于J2EE开发 中的许多业务场景。

  数据共享,还是数据传递

  ThreadLocal模式由于利用了Java自身的语法特性而显得异常简单和方便,因而被广泛使用于J2EE开发,尤其是应对跨层次的资源共享,例如在Spring中,就有使用ThreadLocal模式来管理数据库连接或者Hibernate的Session的范例。

  在一些比较著名的论坛中,有很多关于使用ThreadLocal模式来做数据传递的讨论。事实上,这是对ThreadLocal模式的一个极大的误解。需要注意的是,ThreadLocal模式解决的是同一线程中隶属于不同开发层次的数据共享问题,而不是在不同的开发层次中进行数据传递。

  1)ThreadLocal模式的核心在于实现一个共享环境(类的内部封装了ThreadLocal的静态实例)。所以,在操作ThreadLocal时,这一共享环境会跨越多个开发层次而随处存在。

  2)随处存在的共享环境造成了所有的开发层次的共同依赖,从而使得所有的开发层次都耦合在一起,从而变得无法独立测试。

  3)数据传递应该通过接口函数的签名显式声明,这样才能够从接口声明中表达接口所表达的真正含义。ThreadLocal模式位于实现的内部,从而使得接口与接口之间无法达成一致的声明默契。

  Struts2的解耦合的设计理念使得Struts2的MVC实现成为了使用ThreadLocal模式的天然场所。在第三章中,我们已经介绍了一些基本 概念,Struts2通过引入XWork框架,将整个Http请求的过程拆分成为与Web容器有关和与Web容器无关的两个执行阶段。而这两个阶段的数据 交互就是通过ThreadLocal模式中的线程共享副本安全地进行。在其中,我们没有看到数据传递,存在的只是整个执行线程的数据共享。

   ThreadLocal模式的核心元素

   仔细分析上一节的示意图(图4-1),我们可以发现,要完成ThreadLocal模式,其中最关键的地方就是创建一个任何地方都可以访问到的ThreadLocal实例(也就是执行示意图中的菱形部分)而这一点,我们可以通过类的静态实例变量来实现,这个用于承载静态实例变量的类就被视作是一个共享环境。我们来看一个例子,如代码清单如下所示:

 1 public class Counter {
 2     //新建一个静态的ThreadLocal变量,并通过get方法将其变为一个可访问的对象 
 3     private static ThreadLocal<Integer> counterContext = new ThreadLocal<Integer>(){
 4         protected synchronized Integer initialValue(){
 5             return 10;
 6         }
 7     };
 8     // 通过静态的get方法访问ThreadLocal中存储的值
 9     public static Integer get(){
10         return counterContext.get();
11     }
12     // 通过静态的set方法将变量值设置到ThreadLocal中
13     public static void set(Integer value) {  
14         counterContext.set(value);  
15     } 
16     // 封装业务逻辑,操作存储于ThreadLocal中的变量  
17     public static Integer getNextCounter() {  
18         counterContext.set(counterContext.get() + 1);  
19         return counterContext.get();  
20     }  
21 }

  在这个Counter类中,我们实现了一个静态的ThreadLocal变量,并通过get方法将ThreadLocal中存储的值暴露出来。我们还封装了一个带有业务逻辑的方法getNextCounter,操作ThreadLocal中的值,将其加1,并返回计算后的值。

  此时,Counter类就变成了一个数据共享环境,我们也拥有了实现ThreadLocal模式的关键要素。有了它,我们来编写一个简单的测试,如代码清单如下所示:

1 public class ThreadLocalTest extends Thread {
2     public void run(){
3         for(int i = 0; i < 3; i++){  
4             System.out.println("Thread[" + Thread.currentThread().getName() + "],counter=" + Counter.getNextCounter());  
5         }  
6     }
7 }

  这是一个简单的线程类,循环输出当前线程的名称和getNextCounter的结果,由于getNextCounter中的逻辑所操作的是 ThreadLocal中的变量,所以无论同时有多少个线程在运行,返回的值将仅与当前线程的变量值有关,也就是说,在同一个线程中,变量值会被连续累 加。这一点可以通过如下的测试代码证实:

 1 public class Test {
 2     public static void main(String[] args) {
 3         ThreadLocalTest testThread1 = new ThreadLocalTest();  
 4         ThreadLocalTest testThread2 = new ThreadLocalTest();  
 5         ThreadLocalTest testThread3 = new ThreadLocalTest();  
 6         testThread1.start();  
 7         testThread2.start();  
 8         testThread3.start();
 9     }
10 }

  我们来运行一下上面的代码,并看看输出结果:

  Thread[Thread-2],counter=11
  Thread[Thread-2],counter=12
  Thread[Thread-2],counter=13
  Thread[Thread-0],counter=11
  Thread[Thread-0],counter=12
  Thread[Thread-0],counter=13
  Thread[Thread-1],counter=11
  Thread[Thread-1],counter=12
  Thread[Thread-1],counter=13

  上面的输出结果也证实了,counter的值在多线程环境中的访问是线程安全的。从对例子的分析中我们可以再次体会到,ThreadLocal模式最合适的使用场景:在同一个线程(Thread)的不同开发层次中共享数据。

  从上面的例子中,我们可以简单总结出实现ThreadLocal模式的两个主要步骤:
  1. 建立一个类,并在其中封装一个静态的ThreadLocal变量,使其成为一个共享数据环境。
  2. 在类中实现访问静态ThreadLocal变量的静态方法(设值和取值)。


  建立在ThreadLocal模式的实现步骤之上,ThreadLocal的使用则更加简单。在线程执行的任何地方,我们都可以通过访问共享数据类中所提供的ThreadLocal变量的设值和取值方法安全地获得当前线程中安全的变量值。
  这两个步骤,我们之后会在Struts2的实现中多次提及,读者只要能充分理解ThreadLocal处理多线程访问的基本原理,就能对Struts2的数据访问和数据共享的设计有一个整体的认识。

  讲到这里,我们回过头来看看ThreadLocal模式的引入,到底对我们的编程模型有什么重要的意义呢?

  结论 :使用ThreadLocal模式,可以使得数据在不同的编程层次得到有效地共享。

  这一点,是由ThreadLocal模式的实现机理决定的。因为实现ThreadLocal模式的一个重要步骤,就是构建一个静态的共享存储空间。从而使得任何对象在任何时刻都可以安全地对数据进行访问。

  结论 使用ThreadLocal模式,可以对执行逻辑与执行数据进行有效解耦。

   这一点是ThreadLocal模式给我们带来的最为核心的一个影响。因为在一般情况下,Java对象之间的协作关系,主要通过参数和返回值来进行消息传递,这也是对象协作之间的一个重要依赖。而ThreadLocal模式彻底打破了这种依赖关系,通过线程安全的共享对象来进行数据共享,可以有效避免在编程层次之间形成数据依赖。这也成为了XWork事件处理体系设计的核心。

  

  ----完----

原文地址:https://www.cnblogs.com/gw811/p/2676111.html