Java三大格式化类的线程安全问题以及ThreadLocal的使用
线程安全问题
java.text中的三大格式化类:
1、NumberFormat2、MessageFormat3、DateFormat(SimpleDateFormat)除了NumberFormat外,其他两个都不是线程安全的。NumberFormat中使用的属性都是不变的,而SimpleDateFormat等却使用了可变但没有同步的属性,所以在多线程访问的条件下会产生线程安全问题,即格式不正确的问题。假设我们要提供一个供并发方法的格式化工具方法,需要提供线程安全和高性能,如何做呢?不恰当的使用1、Stack局部变量方式,如?public static String format(Date date) {? ? ? ? SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");? ? ? ? return formater.format(date);}这样在高并发方法下是线程安全的,因为format是stack上分配的变量,不会和其他线程共享,然后不足就是每一次执行格式化,都需要new一个SimpleDateFormat,这样在高并发上,内存消耗不容忽视2、类上静态常量private static final SimpleDateFormat formater = new SimpleDateFormat();......public static String format(Date date) {? ? ? ? return formater.format(date);?}在单线程测试下,格式化1000万记录,第一种方式花费17秒多,而第二种方式仅为7秒多。但在多线程的条件下,这样一来就线程不安全了,怎么办呢?首先想到的时候synchronized,如下所示:public static synchronized String format(Date date) {? ? ? ? //SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");? ? ? ? return formater.format(date);}但这样处理的话,是线程安全了,但使所有并发访问变成了串行化访问,而一个简单的格式化类,用脚趾想也应该是无状态的,支持高性能访问。正确、高效使用综合考虑上面的两种情况,比较完善的实现应该具备以下特征:- 不能每一次格式化都new SimpleDateFormat,但也不能所有线程共用一个SimpleDateFormat实例格式化方法不能增加synchronized限制,增加了synchronized必然降低吞吐量
Accessing shared, mutable data requires using synchronization; one way to avoid this requirement is to not share. If data is only accessed from a single thread, no synchronization is needed. This technique, thread confinement, is one of the simplest ways to achieve thread safety. When an object is confined to a thread, such usage is automatically thread-safe even if the confined object itself is not
通过上面的描述,我们得知,虽然SimpleDateFormat不是线程安全的,但通过把对SimpleDateFormat的访问局限在单个线程,对SimpleDateFormat的使用自然也就线程安全,这种技术叫Thead Configment,Java提供的实现是ThreadLocal。改进改进的方法就是一个线程一个SimpleDateFormat实例,这样每一个线程访问format都是串行化,对SimpleDateFormat的访问自然就线程安全了。同时因应用的所能支持的线程数是有限的,如一般都低于1万(按照实践经验准则支持1万线程高效运行,而不是context switch的话,CPU数目少说也的50核吧),这样SimpleDateFormat的实例也不会太多,对内存的消耗也可以忽略。最后实现方式如下:?private static ThreadLocal<SimpleDateFormat> formaterHolder? ? ? ? ? ? = new ThreadLocal<SimpleDateFormat>() {? ? ? ? ? ? ? ? public SimpleDateFormat initialValue() {? ? ? ? ? ? ? ? ? ? return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");? ? ? ? ? ? ? ? }? ? ? ? ? ? };public static String format(Date date) {? ? ? ? return formaterHolder.get().format(date);}formaterHolder是formater的各个线程的持有者,不同的线程调用formaterHolder.get()获的自己线程的formater。各个线程的formater通过initialValue在线程第一次使用时初始化总结正确写出一个程序比较难,而正确写出一个并发程序更难,而且还不是同一数量级的难度。写出一个好的高并发程序,掌握一门语言仅是一个必要条件,而理解和领会并发编程艺术才是充分条件。