这是有关使用Java编程语言时的拟议实践的系列文章的第一篇。
所有讨论的主题均基于用例,这些用例来自于电信行业的关键任务超高性能生产系统的开发。
在阅读本文的每个部分之前,强烈建议您参考相关的Java API文档以获取详细信息和代码示例。
所有测试均针对具有以下特征的Sony Vaio进行:
- 系统:openSUSE 11.1(x86_64)
- 处理器(CPU):Intel(R)Core(TM)2 Duo CPU T6670 @ 2.20GHz
- 处理器速度:1,200.00 MHz
- 总内存(RAM):2.8 GB
- Java:OpenJDK 1.6.0_0 64位
应用以下测试配置:
- 并发工作者线程:200
- 每个工作人员重复测试的次数:1000
- 整体测试次数:100
在多线程环境中使用DateFormat可能很棘手。 Java API文档明确指出:
“ 日期格式不同步。 建议为每个线程创建单独的格式实例。 如果多个线程同时访问一种格式,则必须在外部进行同步。 ”
典型的情况是使用预定义格式将日期转换为字符串表示形式,反之亦然。 为每次转换创建新的DateFormat实例效率很低。 您应该记住,静态工厂方法“ getDateInstance(..)”在使用时也会创建新的DateFormat实例。 大多数开发人员所做的就是使用DateFormat实现类(例如SimpleDateFormat )构造DateFormat实例,并将其值分配给类变量。 类范围的变量用于其所有日期解析和格式设置需求。 前面提到的方法虽然非常有效,但是由于DateFormat类上缺少同步,因此当多个线程访问类变量的相同实例时会引起问题。 解析创建Date对象时抛出的典型异常是:
- java.lang.NumberFormatException
- java.lang.ArrayIndexOutOfBoundsException
执行格式化时,您还应该遇到格式错误的“ 日期到字符串”表示形式。
为了正确处理上述问题,弄清多线程环境的体系结构至关重要。 Java虚拟机允许应用程序具有多个并发运行的执行线程。 通常,在多线程环境(JVM内的容器或JVM本身)中,应执行线程池。 工作线程应在启动时构造并初始化,以用于执行程序。 例如,一个Web容器构造了一个工作线程池来服务所有传入的流量。 线程池是处理系统资源的最有效方法,主要是因为线程创建和初始化对于Java虚拟机来说是一项非常耗资源的任务。 不过,只需为要同时执行的每段代码创建一个新的执行线程 ,即可实现应用程序并行性。
关于类范围的DateFormat实例:
- 如果你已经明确了NO 线程池将在您的环境中使用,那么只有新的主题实例同时访问你的DateFormat实例。 在这种情况下,建议从外部同步该DateFormat实例
- 如果使用线程池,则可以同时访问DateFormat实例的线程实例数量有限。 因此,建议使用ThreadLocal方法为每个线程创建单独的DateFormat实例。
以下是“ getDateInstance(..)”,“同步”和ThreadLocal方法的示例:
package com.javacodegeeks.test;import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class ConcurrentDateFormatAccess {public Date convertStringToDate(String dateString) throws ParseException {return SimpleDateFormat.getDateInstance(DateFormat.MEDIUM).parse(dateString);}}
package com.javacodegeeks.test;import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class ConcurrentDateFormatAccess {private DateFormat df = new SimpleDateFormat("yyyy MM dd");public Date convertStringToDate(String dateString) throws ParseException {Date result;synchronized(df) {result = df.parse(dateString);}return result;}}
这里要注意的事情:
- 每个执行“ convertStringToDate”操作的线程都试图在获取对DateFormat类变量instance的引用之前获取DateFormat对象的监视器锁定。 如果另一个线程持有该锁,则当前线程将等待直到释放该锁。 这样,一次只有一个线程正在访问DateFormat实例。
package com.javacodegeeks.test;import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class ConcurrentDateFormatAccess {private ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat> () {@Overridepublic DateFormat get() {return super.get();}@Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy MM dd");}@Overridepublic void remove() {super.remove();}@Overridepublic void set(DateFormat value) {super.set(value);}};public Date convertStringToDate(String dateString) throws ParseException {return df.get().parse(dateString);}}
这里要注意的事情:
- 每个执行“ convertStringToDate”操作的线程都将调用“ df.get()”操作,以初始化或检索其本地范围内的DateFormat实例的已初始化引用。
下面,我们给出上述三种方法之间的性能比较表(注意,我们已经测试了DateFormat实用工具类的解析功能。根据特定的日期格式,我们将日期的String表示形式转换为等效的Date对象)。
横轴表示测试运行的次数,纵轴表示每次测试运行的每秒平均事务数(TPS)。 因此,较高的值更好。 如您所见,通过使用线程池和ThreadLocal方法,与“同步”方法和“ getDateInstance(..)”方法相比,您可以获得更高的性能。
最后,我要指出的是,使用不带线程池的ThreadLocal方法等同于使用“ getDateInstance(..)”方法,因为每个新线程都必须在使用之前初始化其本地DateFormat实例,因此需要一个新的DateFormat实例将在每次执行时创建。
编码愉快!
贾斯汀
- Java最佳实践–高性能序列化
- Java最佳实践– Vector vs ArrayList vs HashSet
- Java最佳实践–字符串性能和精确字符串匹配
- Java最佳实践–队列之战和链接的ConcurrentHashMap
- Java最佳实践– Char到Byte和Byte到Char的转换
翻译自: https://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html