zoukankan      html  css  js  c++  java
  • SimpleDateFormat非线程安全

    原文地址:

    http://lilongfei1030.blog.163.com/blog/static/860152820136260822266/

    SimpleDateFormat类的继承关系:

    java.text 

    Class SimpleDateFormat

    java.lang.Object

     |

       +----java.text.Format

     |

       +----java.text.DateFormat

    |

       +----java.text.SimpleDateFormat

    源文档 <http://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>

    该类用来对日期字符串进行解析和格式化输出.

    SimpleDateFormatjavadoc中有这么句话:

    Synchronization

    Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

    源文档 <http://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>

    翻译一下:

    *日期格式是不同步的.

    * 建议为每个线程创建独立的格式实例.

    * 如果多线程并发访问同一个格式,则必须保持外部同步.

    简而言之,SimpleDateFormat不是线程安全的,所以在多线程的环境下,往往会出现意想不到的结果.每次使用时,都创建一个新的SimpleDateFormat实例,或做加锁来同步使用.

    SimpleDateFormat相关问题

    (1)性能问题,主要是创建一个 SimpleDateFormat实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短暂的实例导致性能低下。

    代码如下:

    public class DateUtil {

       

            private static final String FORMAT_YYYMMDD = "yyyy-MM-dd";

        public static String newFormatDate(Date date) {

            return new SimpleDateFormat(FORMAT_YYYMMDD).format(date);

        }

        public static Date newParse(String strDate) throws ParseException {

            return new SimpleDateFormat(FORMAT_YYYMMDD).parse(strDate);

        }

    }

    (2)并发非线程安全问题,即使将 SimpleDateFormat定义为静态类变量,貌似能解决这个问题,但是SimpleDateFormat是非线程安全的,可能引发并发非线程安全问题。

    代码如下:

    public class DateUtil {

         

          private static final SimpleDateFormat SIMPLE_DATE_FORMAT_OBJECT = new SimpleDateFormat("yyyy-MM-dd");

        public static String formatDate(Date date) {

            return SIMPLE_DATE_FORMAT_OBJECT.format(date);

        }

        public static Date parse(String strDate) throws ParseException {

            return SIMPLE_DATE_FORMAT_OBJECT.parse(strDate);

        }

    }

    测试类

    import java.util.Date;

    import java.util.concurrent.ExecutorService;

    import java.util.concurrent.Executors;

    import java.util.concurrent.Semaphore;

    import junit.framework.TestCase;

    import org.apache.log4j.Logger;

    import org.junit.Test;

    /**

     *

     *@Title:SimpleDateFormatTest

     *@Description:SimpleDateFormat类测试

     *@Author:lilongfei

     *@Since:2013-7-20

     *@Version:1.1.0

     */

    public class SimpleDateFormatTest extends TestCase {

        // 线程数

        private static final int THREAD_NUM = 50;

        // 客户端数

        private static final int CLIENT_NUM = 100;

        private static int failCount = 0;

        private static final Logger loger = getDefaultLogger();

        @Override

        public void setUp() throws Exception {

            // TODO: 实现测试前的初始化工作

        }

        @Override

        public void tearDown() throws Exception {

            // 实现测试完成后的垃圾回收、测试结果统计等工作

            loger.info("访问数:" + CLIENT_NUM);

            loger.info("并发数:" + THREAD_NUM);

            loger.info("断言失败数:" + failCount);

        }

        @Test

        public void test() {

            // 得到一个可复用线程的线程池

            ExecutorService exec = Executors.newCachedThreadPool();

            // 信号量

            final Semaphore semp = new Semaphore(THREAD_NUM);

            for (int index = 0; index < CLIENT_NUM; index++) {

                final int no = index;

                Runnable run = new Runnable() {

                    public void run() {

                        // 获取一个准入许可

                        try {

                            semp.acquire();

                            // doFormatTest(no);

                            doParseTest();

                            // 释放一个许可

                            semp.release();

                        } catch (Throwable e) {

                            e.printStackTrace();

                        }

                    }

                };

                // 在线程池中执行一个任务

                exec.execute(run);

            }

            // 退出线程池

            exec.shutdown();

        }

       

        /**

         *

         *

         * @Description:

         */

        private void doParseTest() {

            try {

                DateUtil.parse("2013-07-25");

            } catch (Throwable e) {

                failCount++;

                e.printStackTrace();

            }

        }

       

        /**

         *

         * @param no

         * @Description:

               *    测试时需要修改一下日期,如:当前日期为2013-07-25,明天为2013-07-26

         */

        private void doFormatTest(int no) {

            try {

                if (no % 2 == 0) {

                    String today = DateUtil.formatDate(new Date());

                    assertTrue("ERROR TODAY IS:" + today, "2013-07-25".equals(today));

                } else {

                    String tomorrow = DateUtil.formatDate(new Date(new Date().getTime() + 1000 * 60 * 60 * 24));

                    assertTrue("ERROR TOMORROW IS:" + tomorrow, "2013-07-26".equals(tomorrow));

                }

            } catch (Throwable e) {

                loger.error(e);

                failCount++;

            }

        }

        private static Logger getDefaultLogger() {

            return Logger.getLogger("Businesslog");

        }

    测试用例中使用到log4j,需要在工程根目录下,新建log4j.properties文件

    其中具体内容为

    log4j.rootLogger=DEBUG

    #将逻辑层log记录到BusinessLog,allLog中

    log4j.logger.Businesslog=DEBUG,A1

    #A1--打印到屏幕上

    log4j.appender.A1=org.apache.log4j.ConsoleAppender

    log4j.appender.A1.layout=org.apache.log4j.PatternLayout

    log4j.appender.A1.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%m%n

    引入第三方Jar包,log4j-1.2.15.jarjunit-4.5.jar

    执行结果为:

    java.lang.NumberFormatException: multiple points

    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084)

    at java.lang.Double.parseDouble(Double.java:482)

    at java.text.DigitList.getDouble(DigitList.java:141)

    at java.text.DecimalFormat.parse(DecimalFormat.java:1276)

    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1375)

    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1124)

    at java.text.DateFormat.parse(DateFormat.java:333)

    at com.DateUtil.parse(DateUtil.java:20)

    at com.SimpleDateFormatTest.doParseTest(SimpleDateFormatTest.java:143)

    at com.SimpleDateFormatTest.access$0(SimpleDateFormatTest.java:141)

    at com.SimpleDateFormatTest$1.run(SimpleDateFormatTest.java:71)

    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)

    at java.lang.Thread.run(Thread.java:595)

    java.lang.NumberFormatException: For input string: ""

    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)

    at java.lang.Long.parseLong(Long.java:424)

    at java.lang.Long.parseLong(Long.java:461)

    at java.text.DigitList.getLong(DigitList.java:167)

    at java.text.DecimalFormat.parse(DecimalFormat.java:1271)

    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1692)

    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1124)

    at java.text.DateFormat.parse(DateFormat.java:333)

    at com.DateUtil.parse(DateUtil.java:20)

    at com.SimpleDateFormatTest.doParseTest(SimpleDateFormatTest.java:143)

    at com.SimpleDateFormatTest.access$0(SimpleDateFormatTest.java:141)

    at com.SimpleDateFormatTest$1.run(SimpleDateFormatTest.java:71)

    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)

    at java.lang.Thread.run(Thread.java:595)

    如果用 synchronized 线程同步同样面临性能上的问题,同步将导致性能下降(线程之间序列化的获取SimpleDateFormat实例)。

    使用Threadlocal解决此问题

    以下转载至:源文档 <http://www.oschina.net/question/12_45856> 未做验证。

    对于每个线程SimpleDateFormat不存在影响他们之间协作的状态,为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本,代码如下:

    import java.text.DateFormat;

    import java.text.ParseException;

    import java.text.SimpleDateFormat;

    import java.util.Date;

    /**

    * 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。

    *

    * @author

    *

    */

    public class DateUtil {

    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    @SuppressWarnings("rawtypes")

    private static ThreadLocal threadLocal = new ThreadLocal() {

    protected synchronized Object initialValue() {

    return new SimpleDateFormat(DATE_FORMAT);

    }

    };

    public static DateFormat getDateFormat() {

    return (DateFormat) threadLocal.get();

    }

    public static Date parse(String textDate) throws ParseException {

    return getDateFormat().parse(textDate);

    }

    }

    创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。也可以采用下面方式创建;

    import java.text.DateFormat;

    import java.text.SimpleDateFormat;

    /**

    * 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。

    *

    * @author

    *

    */

    public class DateUtil {

    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    // 第一次调用get将返回null

    private static ThreadLocal threadLocal = new ThreadLocal();

    // 获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中

    public static DateFormat getDateFormat() {

    DateFormat df = (DateFormat) threadLocal.get();

    if (df == null) {

    df = new SimpleDateFormat(DATE_FORMAT);

    threadLocal.set(df);

    }

    return df;

    }

    }

    我们看下我们覆盖的initialValue方法:

    protected T initialValue() {

    return null;//直接返回null

    }

    关于ThreadLocal相关知识可参考:源文档<http://blog.sina.com.cn/s/blog_871746680100yuir.html>

       总之,使用 SimpleDateFormat 应该关注以下几点:

    • 确保不会在多线程状态下使用同一个 DateFormat 或者 SimpleDateFormat 实例
    • 如果多线程情况下需要访问同一个实例,那么请用同步方法
    • 可以使用 JODA 日期时间处理库来避免这些问题
    • 可以使用 commons-lang 包中的 FastDateFormat 工具类

    Commons项目中用来处理Java基本对象方法的工具类包,可以简化很多平时经常要用到的写法,例如判断字符串是否为空等等。

    • 可以使用 ThreadLocal 来处理这个问题
  • 相关阅读:
    LeetCode Best Time to Buy and Sell Stock
    LeetCode Scramble String
    LeetCode Search in Rotated Sorted Array II
    LeetCode Gas Station
    LeetCode Insertion Sort List
    LeetCode Maximal Rectangle
    Oracle procedure
    浏览器下载代码
    Shell check IP
    KVM- 存储池配置
  • 原文地址:https://www.cnblogs.com/alexlo/p/4027168.html
Copyright © 2011-2022 走看看