收藏私塾在线
 

欢迎您来到私塾在线网!   

请登录! 

免费注册 


zhang的笔记
状态: 离线
人气:5023378
访问用户量:4221
笔记经验:
总积分:261656
级别:VIP5
搜索本笔记
ta的交流分类
ta的交流主题贴(544)
ta的所有交流贴(1049)
ta的全部笔记
全部笔记(255)
未分类笔记(1)
Java Web(9)
并发实践(1)
课程问题(0)
Java(22)
架构(1)
缓存(5)
JavaEE(0)
JVM(12)
跟我学spring3(68)
Spring Sec……(43)
Spring 3.x……(25)
Spring Sec……(20)
跟开涛学Spring……(17)
深入剖析Spring……(18)
性能调优(10)
前端(2)
Tomcat源码解读(1)
spring sec……(0)
存档
2014-01(7)
2013-12(10)
2012-10(4)
2012-09(2)
2012-08(31)
2012-07(10)
2012-06(5)
2012-05(41)
2012-04(3)
2012-03(41)
2012-02(54)
2011-11(17)
2011-10(30)

2012-06-01 16:37:15
学习Spring必学的Java基础知识(6)----ThreadLocal
浏览(8756)|评论(0)   交流分类:Java|笔记分类: Spring 3.x……

我们知道Spring通过各种模板类降低了开发者使用各种数据持久技术的难度。这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突。我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源。但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享。虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题。 
按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步。但模板类并未采用线程同步机制,因为线程同步会降低并发性,影响系统性能。此外,通过代码同步解决线程安全的挑战性很大,可能会增强好几倍的实现难度。那么模板类究竟仰仗何种魔法神功,可以在无须线程同步的情况下就化解线程安全的难题呢?答案就是ThreadLocal! 
ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的作用。要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡垒。 

ThreadLocal是什么 

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。 
ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。 

线程局部变量并不是Java的新发明,很多语言(如IBM XL、FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供语言级支持,而以一种变通的方法,通过ThreadLocal的类提供支持。所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,这也是为什么线程局部变量没有在Java开发者中得到很好普及的原因。 


ThreadLocal的接口方法 

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下。 

    • void set(Object value)
    •    设置当前线程的线程局部变量的值;
    • public Object get()
    •    该方法返回当前线程所对应的线程局部变量;
    • public void remove()
    •    将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
    • protected Object initialValue()
    •    返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的默认实现直接返回一个null。 




值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。 

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本: 

代码清单9-3  SimpleThreadLocal 

Java代码    收藏代码
  1. public class SimpleThreadLocal {  
  2.     private Map valueMap = Collections.synchronizedMap(new HashMap());  
  3.     public void set(Object newValue) {  
  4.                 //①键为线程对象,值为本线程的变量副本  
  5.         valueMap.put(Thread.currentThread(), newValue);  
  6.     }  
  7.     public Object get() {  
  8.         Thread currentThread = Thread.currentThread();  
  9.   
  10.                 //②返回本线程对应的变量  
  11.         Object o = valueMap.get(currentThread);   
  12.                   
  13.                 //③如果在Map中不存在,放到Map中保存起来  
  14.                if (o == null && !valueMap.containsKey(currentThread)) {  
  15.             o = initialValue();  
  16.             valueMap.put(currentThread, o);  
  17.         }  
  18.         return o;  
  19.     }  
  20.     public void remove() {  
  21.         valueMap.remove(Thread.currentThread());  
  22.     }  
  23.     public Object initialValue() {  
  24.         return null;  
  25.     }  
  26. }  



虽然代码清单9 3中这个ThreadLocal实现版本显得比较幼稚,但它和JDK所提供的ThreadLocal类在实现思路上是非常相近的。 

一个TheadLocal实例 

下面,我们通过一个具体的实例了解一下ThreadLocal的具体使用方法。 

代码清单9-4  SequenceNumber 

Java代码    收藏代码
  1. package com.baobaotao.basic;  
  2.   
  3. public class SequenceNumber {  
  4.        
  5.         //①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  
  6.     private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){  
  7.         public Integer initialValue(){  
  8.             return 0;  
  9.         }  
  10.     };  
  11.        
  12.         //②获取下一个序列值  
  13.     public int getNextNum(){  
  14.         seqNum.set(seqNum.get()+1);  
  15.         return seqNum.get();  
  16.     }  
  17.       
  18.     public static void main(String[ ] args)   
  19.     {  
  20.           SequenceNumber sn = new SequenceNumber();  
  21.            
  22.          //③ 3个线程共享sn,各自产生序列号  
  23.          TestClient t1 = new TestClient(sn);    
  24.          TestClient t2 = new TestClient(sn);  
  25.          TestClient t3 = new TestClient(sn);  
  26.          t1.start();  
  27.          t2.start();  
  28.          t3.start();  
  29.     }     
  30.     private static class TestClient extends Thread  
  31.     {  
  32.         private SequenceNumber sn;  
  33.         public TestClient(SequenceNumber sn) {  
  34.             this.sn = sn;  
  35.         }  
  36.         public void run()  
  37.         {  
  38.                         //④每个线程打出3个序列值  
  39.             for (int i = 0; i < 3; i++) {  
  40.             System.out.println("thread["+Thread.currentThread().getName()+  
  41. "] sn["+sn.getNextNum()+"]");  
  42.             }  
  43.         }  
  44.     }  
  45. }  



通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果: 

引用
thread[Thread-2] sn[1] 
thread[Thread-0] sn[1] 
thread[Thread-1] sn[1] 
thread[Thread-2] sn[2] 
thread[Thread-0] sn[2] 
thread[Thread-1] sn[2] 
thread[Thread-2] sn[3] 
thread[Thread-0] sn[3] 
thread[Thread-1] sn[3]



考查输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个Sequence Number实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。 

与Thread同步机制的比较 

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。 

在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。 

而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。 

由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度上简化ThreadLocal的使用,代码清单9-2就使用了JDK 5.0新的ThreadLocal<T>版本。 

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 

Spring使用ThreadLocal解决线程安全问题 

我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。 

一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9-2所示。 

 

这样用户就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有对象所访问的同一ThreadLocal变量都是当前线程所绑定的。 
下面的实例能够体现Spring对有状态Bean的改造思路: 

代码清单9-5  TopicDao:非线程安全 

Java代码    收藏代码
  1. public class TopicDao {  
  2.    //①一个非线程安全的变量  
  3.    private Connection conn;   
  4.    public void addTopic(){  
  5.         //②引用非线程安全变量  
  6.        Statement stat = conn.createStatement();  
  7.        …  
  8.    }  
  9. }  



由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造: 

代码清单9-6  TopicDao:线程安全 

Java代码    收藏代码
  1. import java.sql.Connection;  
  2. import java.sql.Statement;  
  3. public class TopicDao {  
  4.   
  5.   //①使用ThreadLocal保存Connection变量  
  6. private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();  
  7. public static Connection getConnection(){  
  8.            
  9.         //②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,  
  10.         //并将其保存到线程本地变量中。  
  11. if (connThreadLocal.get() == null) {  
  12.             Connection conn = ConnectionManager.getConnection();  
  13.             connThreadLocal.set(conn);  
  14.               return conn;  
  15.         }else{  
  16.               //③直接返回线程本地变量  
  17.             return connThreadLocal.get();  
  18.         }  
  19.     }  
  20.     public void addTopic() {  
  21.   
  22.         //④从ThreadLocal中获取线程对应的  
  23.          Statement stat = getConnection().createStatement();  
  24.     }  
  25. }  



不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否为null,如果为null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其他线程的Connection。因此,这个TopicDao就可以做到singleton共享了。 

当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在Dao只能做到本Dao的多个方法共享Connection时不发生线程安全问题,但无法和其他Dao共用同一个Connection,要做到同一事务多Dao共享同一个Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。但这个实例基本上说明了Spring对有状态类线程安全化的解决思路。在本章后面的内容中,我们将详细说明Spring如何通过ThreadLocal解决事务管理的问题。 

 


http://sishuok.com/forum/blogPost/list/0/4477.html#13914

 

 

转载自《Spring 3.x企业实用开发实战》http://stamen.iteye.com/blog/1535120

作者博客:http://stamen.iteye.com/blog/profile

作者介绍: 陈雄华  老程序员一枚,做了10的Java开发,对Spring,Oracle,云计算,敏捷开发感兴趣。是中图一购网www.bookegou.com的联合创始人之一。

购买地址:http://product.china-pub.com/198906


相关笔记推荐
精品视频课程推荐

深入浅出学Shrio视频教程
内容概述:Shiro是目前最热门、最易用、功能超强大的Java权限管理框架,强烈推荐,每个项目都必备的权限管理技术!通过本课程,你将从零开始直到彻底掌握Shiro的相关开发知识,达到可以进行实际项目开发的能力。包括:权限管理基础、Shiro入门、配置、身份认证、授权、Realms、Session管理、和Spring的集成、Web、Cache等众多开发细节技术 技术要点:源码级分析Shiro的授权过程、自定义开发Realm、多个Realms的开发配置、自定义开发AuthenticationStrategy、自定义开发自定义SessionDAO、和Struts2+Spring3的集成(包括修正struts2的bug)、Shiro和SpringMVC+Spring3的集成、包装使用其他的Cache框架、缓存数据同步更新的解决方案等等实际开发中常用的内容

研磨设计模式——跟着cc学设计系列视频教程
本视频课程是北京Java私塾原创精品书籍《研磨设计模式》一书的配套学习视频,由《研磨设计模式》的第一作者CC录制 课程目标:全面、系统的掌握GoF设计模式的知识,达到可以在实际项目开发中运用的能力 技术要点:如何实现可配置、如何实现缓存以及缓存的管理、如何实现用缓存来控制多实例的创建、如何实现参数化工厂、 如何实现可扩展工厂、如何实现原型管理器、如何实现Java的静态代理和动态代理、如何实现多线程处理队列请求、 如何实现命令的参数化配置、可撤销的操作、宏命令、队列请求和日志请求、如何实现翻页迭代、如何检测环状结构、 如何实现通用的增删改查、如何模拟工作流来处理流程、如何实现简单又通用的XML读取、如何实现模拟AOP的功能......

Ajax+JSON基础实战视频教程
数据校验、Javascript模拟多线程、下拉列表联动、操作XML、AJAX结合JSON的操作、Json-lib的使用

Weblogic实战视频教程
WebLogic基础知识:WebLogic基本概念、正确安装WebLogic、建域、应用部署于JDBC选择、对WebLogic的监控和日志查看、集群的高可用性;课程目标:彻底掌握WebLogic的基本概念,在理解基本概念的基础上做到正确的安装WebLogic,根据不同的需求创建域,合理选择应用部署和JDBC配置。熟练掌握WebLogic的console监控,了解各种性能和运行指标,以及对监控结果的分析,运用集群的高可用性,对集群架设。

深入浅出学Spring Web MVC视频教程
系统、完整的学习Spring Web MVC开发的知识。包括:Spring Web MVC入门;理解DispatcherServlet;注解式控制器开发详解;数据类型转换;数据格式化;数据验证; 拦截器;对Ajax的支持;文件上传下载;表单标签等内容;最后以一个综合的CRUD带翻页的应用示例来综合所学的知识

浏览(8756)|评论(0)   交流分类:Java|笔记分类: Spring 3.x……

评论(0)
请登录后评论 登录

关于我们 | 联系我们 | 用户协议 | 私塾在线服务协议 | 版权声明 | 隐私保护

版权所有 Copyright(C)2009-2012 私塾在线学习网