线程安全问题的由来java
在传统的Web开发中,咱们处理Http请求最经常使用的方式是经过实现Servlet对象来进行Http请求的响应.Servlet是J2EE的重要标准之一,规定了Java如何响应Http请求的规范.经过HttpServletRequest和HttpServletResponse对象,咱们可以轻松地与Web容器交互.编程
当Web容器收到一个Http请求时,Web容器中的一个主调度线程会从事先定义好的线程中分配一个当前工做线程,将请求分配给当前的工做线程,由该线程来执行对应的Servlet对象中的service方法.当这个工做线程正在执行的时候,Web容器收到另一个请求,主调度线程会一样从线程池中选择另一个工做线程来服务新的请求.Web容器自己并不关心这个新的请求是否访问的是同一个Servlet实例.所以,咱们能够得出一个结论:对于同一个Servlet对象的多个请求,Servlet的service方法将在一个多线程的环境中并发执行.因此,Web容器默认采用单实例(单Servlet实例)多线程的方式来处理Http请求.这种处理方式可以减小新建Servlet实例的开销,从而缩短了对Http请求的响应时间.可是,这样的处理方式会致使变量访问的线程安全问题.也就是说,Servlet对象并非一个线程安全的对象.安全
01.package com.qingdao.proxy; 02. 03.public class ThreadSafeTestServlet extends HttpServlet { 04. // 定义一个实例变量,并不是一个线程安全的变量 05. private int counter = 0; 06. 07. public void doGet(HttpServletRequest req, HttpServletResponse resp) 08. throws ServletException, IOException { 09. doPost(req, resp); 10. } 11. 12. public void doPost(HttpServletRequest req, HttpServletResponse resp) 13. throws ServletException, IOException { 14. // 输出当前Servlet的信息以及当前线程的信息 15. System.out.println(this + ":" + Thread.currentThread()); 16. // 循环,并增长实例变量counter的值 17. for (int i = 0; i < 5; i++) { 18. System.out.println("Counter = " + counter); 19. try { 20. Thread.sleep((long) Math.random() * 1000); 21. counter++; 22. } catch (InterruptedException exc) { 23. } 24. } 25. } 26.}
sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor23,5,main]
Counter = 60
Counter = 61
Counter = 62
Counter = 65
Counter = 68
Counter = 71
Counter = 74
Counter = 77
Counter = 80
Counter = 83
sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor22,5,main]
Counter = 61
Counter = 63
Counter = 66
Counter = 69
Counter = 72
Counter = 75
Counter = 78
Counter = 81
Counter = 84
Counter = 87
sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor24,5,main]
Counter = 61
Counter = 64
Counter = 67
Counter = 70
Counter = 73
Counter = 76
Counter = 79
Counter = 82
Counter = 85
Counter = 88
01.public class Thread implements Runnable { 02. // 这里省略了许多其余的代码 03. ThreadLocal.ThreadLocalMap threadLocals = null; 04.}
这是JDK中Thread源码的一部分,从中咱们能够看出ThreadLocalMap跟随着当前的线程而存在.不一样的线程Thread,拥有不一样的ThreadLocalMap的本地实例变量,这也就是“副本”的含义.接下来咱们再来看看ThreadLocal.ThreadLocalMap是如何定义的,以及ThreadLocal如何来操做它数据结构
01.public class ThreadLocal<T> { 02. 03. // 这里省略了许多其余代码 04. 05. // 将value的值保存于当前线程的本地变量中 06. public void set(T value) { 07. // 获取当前线程 08. Thread t = Thread.currentThread(); 09. // 调用getMap方法得到当前线程中的本地变量ThreadLocalMap 10. ThreadLocalMap map = getMap(t); 11. // 若是ThreadLocalMap已存在,直接使用 12. if (map != null) 13. // 以当前的ThreadLocal的实例做为key,存储于当前线程的 14. // ThreadLocalMap中,若是当前线程中被定义了多个不一样的ThreadLocal 15. // 的实例,则它们会做为不一样key进行存储而不会互相干扰 16. map.set(this, value); 17. else 18. // ThreadLocalMap不存在,则为当前线程建立一个新的 19. createMap(t, value); 20. } 21. 22. // 获取当前线程中以当前ThreadLocal实例为key的变量值 23. public T get() { 24. // 获取当前线程 25. Thread t = Thread.currentThread(); 26. // 获取当前线程中的ThreadLocalMap 27. ThreadLocalMap map = getMap(t); 28. if (map != null) { 29. // 获取当前线程中以当前ThreadLocal实例为key的变量值 30. ThreadLocalMap.Entry e = map.getEntry(this); 31. if (e != null) 32. return (T) e.value; 33. } 34. // 当map不存在时,设置初始值 35. return setInitialValue(); 36. } 37. 38. // 从当前线程中获取与之对应的ThreadLocalMap 39. ThreadLocalMap getMap(Thread t) { 40. return t.threadLocals; 41. } 42. 43. // 建立当前线程中的ThreadLocalMap 44. void createMap(Thread t, T firstValue) { 45. // 调用构造函数生成当前线程中的ThreadLocalMap 46. t.threadLocals = new ThreadLocalMap(this, firstValue); 47. } 48. 49. // ThreadLoaclMap的定义 50. static class ThreadLocalMap { 51. // 这里省略了许多代码 52. } 53.}
深刻比较TheadLocal模式与synchronized关键字多线程
ThreadLocal模式synchronized关键字都用于处理多线程并发访问变量的问题,只是两者处理问题的角度和思路不一样.并发
1)ThreadLocal是一个java类,经过对当前线程中的局部变量的操做来解决不一样线程的变量访问的冲突问题.因此,ThreadLocal提供了线程安全的共享对象机制,每一个线程都拥有其副本.dom
2)Java中的synchronized是一个保留字,它依靠JVM的锁机制来实现临界区的函数或者变量的访问中的原子性.在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量.此时,被用做“锁机制”的变量时多个线程共享的.函数
同步机制(synchronized关键字)采用了“以时间换空间”的方式,提供一份变量,让不一样的线程排队访问.而ThreadLocal采用了“以空间换时间”的方式,为每个线程都提供一份变量的副本,从而实现同时访问而互不影响测试
ThreadLocal模式的核心元素this
要完成ThreadLocal模式,其中最关键的地方就是建立一个任何地方均可以访问到的ThreadLocal实例(也就是执行示意图中的菱形部分).而这一点,咱们能够经过类的静态实例变量来实现,这个用于承载静态实例变量的类就被视做是一个共享环境.咱们来看一个例子,如代码清单以下所示:
01.public class Counter { 02. //新建一个静态的ThreadLocal变量,并经过get方法将其变为一个可访问的对象 03. private static ThreadLocal<Integer> counterContext = new ThreadLocal<Integer>(){ 04. protected synchronized Integer initialValue(){ 05. return 10; 06. } 07. }; 08. // 经过静态的get方法访问ThreadLocal中存储的值 09. 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. }
01.public class ThreadLocalTest extends Thread { 02. public void run(){ 03. for(int i = 0; i < 3; i++){ 04. System.out.println("Thread[" + Thread.currentThread().getName() + "],counter=" + Counter.getNextCounter()); 05. } 06. } 07. }
01.public class Test { 02. public static void main(String[] args) { 03. ThreadLocalTest testThread1 = new ThreadLocalTest(); 04. ThreadLocalTest testThread2 = new ThreadLocalTest(); 05. ThreadLocalTest testThread3 = new ThreadLocalTest(); 06. testThread1.start(); 07. testThread2.start(); 08. testThread3.start(); 09. } 10. }
输出结果:
上面的输出结果也证明了,counter的值在多线程环境中的访问是线程安全的.从对例子的分析中咱们能够再次体会到,ThreadLocal模式最合适的使用场景:在同一个线程(Thread)的不一样开发层次中共享数据.
从上面的例子中,咱们能够简单总结出实现ThreadLocal模式的两个主要步骤:
1. 创建一个类,并在其中封装一个静态的ThreadLocal变量,使其成为一个共享数据环境.
2. 在类中实现访问静态ThreadLocal变量的静态方法(设值和取值).
创建在ThreadLocal模式的实现步骤之上,ThreadLocal的使用则更加简单.在线程执行的任何地方,咱们均可以经过访问共享数据类中所提供的ThreadLocal变量的设值和取值方法安全地得到当前线程中安全的变量值.
这两个步骤,咱们以后会在Struts2的实现中屡次说起,读者只要能充分理解ThreadLocal处理多线程访问的基本原理,就能对Struts2的数据访问和数据共享的设计有一个总体的认识.
讲到这里,咱们回过头来看看ThreadLocal模式的引入,到底对咱们的编程模型有什么重要的意义呢?
结论 :使用ThreadLocal模式,可使得数据在不一样的编程层次获得有效地共享,
这一点,是由ThreadLocal模式的实现机理决定的.由于实现ThreadLocal模式的一个重要步骤,就是构建一个静态的共享存储空间.从而使得任何对象在任什么时候刻均可以安全地对数据进行访问.
结论 使用ThreadLocal模式,能够对执行逻辑与执行数据进行有效解耦
这一点是ThreadLocal模式给咱们带来的最为核心的一个影响,由于在通常状况下,Java对象之间的协做关系,主要经过参数和返回值来进行消息传递,这也是对象协做之间的一个重要依赖,而ThreadLocal模式完全打破了这种依赖关系,经过线程安全的共享对象来进行数据共享,能够有效避免在编程层次之间造成数据依赖,这也成为了XWork事件处理体系设计的核心.
ThreadLocal 多线程共享变量 的隔离
线程内部有ThreadLocalMap 属性 ,而ThreadLocalMap是ThreadLocal的静态内部类
横向 线程与线程之间经过Thread线程内部ThradLocalMap 隔离;
纵向 变量与变量之间经过不一样ThreadLocal对象隔离
ThreadLocalMap 内部以this指针根据当前ThreadLocal对象为键存储,实现多个变量的隔离