这段时间在作项目的时候,考虑到Spring中的bean默认是单例模式的,那么当多个线程调用同一个bean的时候就会存在线程安全问题。若是是Spring中bean的建立模式为非单例的,也就不存在这样的问题了。java
Spring 框架里的 bean ,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤为注意的地方。 单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。这个类称为单例类。缓存
当多用户同时请求一个服务时,容器会给每个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法),此时就要注意了,若是该处理逻辑中有对该单列状态的修改(体现为该单列的成员属性),则必须考虑线程同步问题。安全
同步机制的比较: ThreadLocal 和线程同步机制相比有什么优点呢? ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。 多线程
在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析何时对变量进行读写,何时须要锁定某个对象,何时释放对象锁等繁杂的问题,程序设计和编写难度相对较大。并发
而 ThreadLocal 则从另外一个角度来解决多线程的并发访问。 ThreadLocal 会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。由于每个线程都拥有本身的变量副本,从而也就没有必要对该变量进行同步了。 ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,能够把不安全的变量封装进ThreadLocal 。 框架
因为 ThreadLocal 中能够持有任何类型的对象,低版本 JDK 所提供的 get() 返回的是 Object 对象,须要强制类型转换。但 JDK 5.0 经过泛型很好的解决了这个问题,在必定程度地简化 ThreadLocal 的使用。 括起来讲,对于多线程资源共享的问题,同步机制采用了 “ 以时间换空间 ” 的方式,而 ThreadLocal 采用了 “ 以空间换时间 ” 的方式。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。 less
Spring 使用 ThreadLocal 解决线程安全问题 。 咱们知道在通常状况下,只有无状态的 Bean 才能够在多线程环境下共享,在 Spring 中,绝大部分 Bean 均可以声明为singleton 做用域。就是由于 Spring 对一些 Bean (如 RequestContextHolder 、TransactionSynchronizationManager 、 LocaleContextHolder 等)中非线程安全状态采用 ThreadLocal 进行处理,让它们也成为线程安全的状态,由于有状态的 Bean 就能够在多线程中共享了。 ide
通常的 Web 应用划分为展示层、服务层和持久层三个层次,在不一样的层中编写对应的逻辑,下层经过接口向上层开放功能调用。在通常状况下,从接收请求到返回响应所通过的全部程序调用都同属于一个线程。性能
ThreadLocal 是解决线程安全问题一个很好的思路,它经过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在不少状况下, ThreadLocal 比直接使用 synchronized 同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。 测试
若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。 或者说 : 一个类或者程序所提供的接口对于线程来讲是原子操做或者多个线程之间的切换不会致使该接口的执行结果存在二义性 , 也就是说咱们不用考虑同步的问题。 线程安全问题都是由全局变量及静态变量引发的。
若每一个线程中对全局变量、静态变量只有读操做,而无写操做,通常来讲,这个全局变量是线程安全的;如有多个线程同时执行写操做,通常都须要考虑线程同步,不然就可能影响线程安全。
1 ) 常量始终是线程安全的,由于只存在读操做。
2 )每次调用方法前都新建一个实例是线程安全的,由于不会访问共享的资源。
3 )局部变量是线程安全的。由于每执行一个方法,都会在独立的空间建立局部变量,它不是共享的资源。局部变量包括方法的参数变量和方法内变量。
有状态就是有数据存储功能。有状态对象 (Stateful Bean) ,就是有实例变量的对象 ,能够保存数据,是非线程安全的。在不一样方法调用间不保留任何状态。
无状态就是一次操做,不能保存数据。无状态对象 (Stateless Bean) ,就是没有实例变量的对象 . 不能保存数据,是不变类,是线程安全的。
有状态对象 :
无状态的 Bean 适合用不变模式,技术就是单例模式,这样能够共享实例,提升性能。有状态的 Bean ,多线程环境下不安全,那么适合用 Prototype 原型模式。Prototype: 每次对 bean 的请求都会建立一个新的 bean 实例。
Struts2 默认的实现是 Prototype 模式。也就是每一个请求都新生成一个 Action 实例,因此不存在线程安全问题。须要注意的是,若是由 Spring 管理 action 的生命周期, scope 要配成 prototype 做用域。
SimpleDateFormat( 下面简称 sdf) 类内部有一个 Calendar 对象引用 , 它用来储存和这个 sdf 相关的日期信息 , 例如 sdf.parse(dateStr), sdf.format(date) 诸如此类的方法参数传入的日期相关 String, Date 等等 , 都是交友 Calendar 引用来储存的 . 这样就会致使一个问题 , 若是你的 sdf 是个 static 的 , 那么多个 thread 之间就会共享这个 sdf, 同时也是共享这个 Calendar 引用 , 而且 , 观察 sdf.parse() 方法 , 你会发现有以下的调用 :
1 Date parse() { 2 calendar.clear(); // 清理calendar 3 ... // 执行一些操做, 设置 calendar 的日期什么的 4 calendar.getTime(); // 获取calendar的时间 5 }
这里会致使的问题就是 , 若是 线程 A 调用了 sdf.parse(), 而且进行了 calendar.clear() 后还未执行 calendar.getTime() 的时候 , 线程 B 又调用了 sdf.parse(), 这时候线程 B 也执行了 sdf.clear() 方法 , 这样就致使线程 A 的的 calendar 数据被清空了 ( 实际上 A,B 的同时被清空了 ). 又或者当 A 执行了 calendar.clear() 后被挂起 , 这时候 B 开始调用 sdf.parse() 并顺利 i 结束 , 这样 A 的 calendar 内存储的的 date 变成了后来 B 设置的 calendar 的 date
这个问题背后隐藏着一个更为重要的问题 -- 无状态:无状态方法的好处之一,就是它在各类环境下,均可以安全的调用。衡量一个方法是不是有状态的,就看它是否改动了其它的东西,好比全局变量,好比实例的字段。 format 方法在运行过程当中改动了SimpleDateFormat 的 calendar 字段,因此,它是有状态的。
这也同时提醒咱们在开发和设计系统的时候注意下一下三点 :
1. 本身写公用类的时候,要对多线程调用状况下的后果在注释里进行明确说明
2. 对线程环境下,对每个共享的可变变量都要注意其线程安全性
3. 咱们的类和方法在作设计的时候,要尽可能设计成无状态的
1. 须要的时候建立新实例:
说明:在须要用到 SimpleDateFormat 的地方新建一个实例,无论何时,将有线程安全问题的对象由共享变为局部私有都能避免多线程问题,不过也加剧了建立对象的负担。在通常状况下,这样其实对性能影响比不是很明显的。
2. 使用同步:同步 SimpleDateFormat 对象
1 public class DateSyncUtil { 2 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 3 4 public static String formatDate(Date date)throws ParseException{ 5 synchronized(sdf){ 6 return sdf.format(date); 7 } 8 } 9 10 public static Date parse(String strDate) throws ParseException{ 11 synchronized(sdf){ 12 return sdf.parse(strDate); 13 } 14 } 15 }
说明:当线程较多时,当一个线程调用该方法时,其余想要调用此方法的线程就要block ,多线程并发量大的时候会对性能有必定的影响。
3. 使用 ThreadLocal :
1 public class ConcurrentDateUtil { 2 private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { 3 @Override 4 protected DateFormat initialValue() { 5 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 6 } 7 }; 8 public static Date parse(String dateStr) throws ParseException { 9 return threadLocal.get().parse(dateStr); 10 } 11 public static String format(Date date) { 12 return threadLocal.get().format(date); 13 } 14 }
或
1 ThreadLocal<DateFormat>(); 2 3 public static DateFormat getDateFormat() 4 { 5 DateFormat df = threadLocal.get(); 6 if(df==null){ 7 df = new SimpleDateFormat(date_format); 8 threadLocal.set(df); 9 } 10 return df; 11 } 12 public static String formatDate(Date date) throws ParseException { 13 return getDateFormat().format(date); 14 } 15 public static Date parse(String strDate) throws ParseException { 16 return getDateFormat().parse(strDate); 17 } 18 }
说明:使用 ThreadLocal, 也是将共享变量变为独享,线程独享确定能比方法独享在并发环境中能减小很多建立对象的开销。若是对性能要求比较高的状况下,通常推荐使用这种方法。
4. 抛弃 JDK ,使用其余类库中的时间格式化类:
1. 使用 Apache commons 里的 FastDateFormat ,宣称是既快又线程安全的SimpleDateFormat, 惋惜它只能对日期进行 format, 不能对日期串进行解析。
2. 使用 Joda-Time 类库来处理时间相关问题
作一个简单的压力测试,方法一最慢,方法三最快,可是就算是最慢的方法一性能也不差,通常系统方法一和方法二就能够知足,因此说在这个点很难成为你系统的瓶颈所在。从简单的角度来讲,建议使用方法一或者方法二,若是在必要的时候,追求那么一点性能提高的话,能够考虑用方法三,用 ThreadLocal 作缓存。
Joda-Time 类库对时间处理方式比较完美,建议使用。