SimpleDateFormatセキュリティの時間フォーマットスレッドセキュリティの問題について


詳細
Javaでの日付フォーマットはSimpleDateFormatによく使われ、SimpleDateFormatをstaticタイプの変数として定義することが多く、他の場所で使用されやすいが、マルチスレッドアプリケーションではスレッドが安全ではないという問題が発生するが、なぜスレッドが安全ではないという問題が発生するのか.次の仁兄の説明に感謝します.
転載:https://blog.csdn.net/zxh87/article/details/19414885
このようなインスタンスを作成するには大きなコストがかかるため、プログラムでSimpleDateFormatインスタンスをできるだけ少なく作成する必要があることを知っています.データベースデータを読み込んでexcelファイルにエクスポートする例では、時間情報を処理するたびにSimpleDateFormatインスタンスオブジェクトを作成し、そのオブジェクトを破棄する必要があります.大量のオブジェクトが作成され、大量のメモリとjvm空間が消費されます.コードは次のとおりです.
package com.peidasoft.dateformat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
    
    public static  String formatDate(Date date)throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }
    
    public static Date parse(String strDate) throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    }
}

OKと言うかもしれませんが、静的simpleDateFormatインスタンスを作成し、DateUtilクラス(以下)に配置して、使用時にこのインスタンスを直接使用して操作すると、問題が解決します.改善されたコードは次のとおりです.
package com.peidasoft.dateformat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
    private static final  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    public static  String formatDate(Date date)throws ParseException{
        return sdf.format(date);
    }
    
    public static Date parse(String strDate) throws ParseException{

        return sdf.parse(strDate);
    }
}

もちろん、この方法は確かによくて、ほとんどの時間の中でよく働いています.しかし、本番環境でしばらく使用すると、スレッドが安全ではないという事実に気づきます.通常のテストでは問題ありませんが、生産環境で一定の負荷がかかると、この問題が発生します.彼は転化の時間が正しくない、例えば新聞の間違いなど、いろいろな状況が現れます.次のテスト例を見てみましょう
package com.peidasoft.dateformat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
    
    private static final  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    public static  String formatDate(Date date)throws ParseException{
        return sdf.format(date);
    }
    
    public static Date parse(String strDate) throws ParseException{

        return sdf.parse(strDate);
    }
}

 
package com.peidasoft.dateformat;

import java.text.ParseException;
import java.util.Date;

public class DateUtilTest {
    
    public static class TestSimpleDateFormatThreadSafe extends Thread {
        @Override
        public void run() {
            while(true) {
                try {
                    this.join(2000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                try {
                    System.out.println(this.getName()+":"+DateUtil.parse("2013-05-24 06:02:20"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        }    
    }
    
    
    public static void main(String[] args) {
        for(int i = 0; i < 3; i++){
            new TestSimpleDateFormatThreadSafe().start();
        }
            
    }
}

 
実行出力は次のとおりです.
Exception in thread "Thread-1"java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082)
at java.lang.Double.parseDouble(Double.java:510)
at java.text.DigitList.getDouble(DigitList.java:151)
at java.text.DecimalFormat.parse(DecimalFormat.java:1302)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311)
at java.text.DateFormat.parse(DateFormat.java:335)
at com.peidasoft.orm.dateformat.DateNoStaticUtil.parse(DateNoStaticUtil.java:17)
at com.peidasoft.orm.dateformat.DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:20)
Exception in thread "Thread-0"java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082)
at java.lang.Double.parseDouble(Double.java:510)
at java.text.DigitList.getDouble(DigitList.java:151)
at java.text.DecimalFormat.parse(DecimalFormat.java:1302)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311)
at java.text.DateFormat.parse(DateFormat.java:335)
at com.peidasoft.orm.dateformat.DateNoStaticUtil.parse(DateNoStaticUtil.java:17)
at com.peidasoft.orm.dateformat.DateUtilTest$TestSimpleDateFormatThreadSafe.run(DateUtilTest.java:20)
Thread-2:Mon May 24 06:02:20 CST 2021
Thread-2:Fri May 24 06:02:20 CST 2013
Thread-2:Fri May 24 06:02:20 CST 2013
Thread-2:Fri May 24 06:02:20 CST 2013
説明:Thread-1とThread-0はjavaを報告します.lang.NumberFormatException:multiple pointsエラー、直接掛けて、起きていません;Thread-2は死んでいませんが、出力の時間は間違っています.例えば、入力した時間は2013-05-24 06:02:20で、Mon May 24 06:02:20 CST 2021のような霊的なイベントが出力されます.
二.の原因となる
プロのプログラマーとして、変数を共有するよりも、新しい変数を作成するたびにコストがかかることは当然知っています.上の最適化された静的SimpleDateFormat版は、SimpleDateFormatクラスとDateFormatクラスがスレッドセキュリティではないため、同時に様々な霊的なエラーが発生します.スレッドセキュリティの問題を無視しているのは、SimpleDateFormatクラスとDateFormatクラスが提供してくれたインタフェースから見ると、スレッドセキュリティと何の関係があるのか分からないからです.ただし、JDKドキュメントの一番下には次のような説明があります.
SimpleDateFormatの日付フォーマットは同期されていません.推奨(推奨)各スレッドに独立したフォーマットインスタンスを作成します.複数のスレッドが同時に1つのフォーマットにアクセスする場合は、外部同期を維持する必要があります.
JDKの元の文書は次のとおりです: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.
次に、JDKソースコードを見て、SimpleDateFormatクラスとDateFormatクラスがスレッドセキュリティの本当の原因ではない理由を見てみましょう.
SimpleDateFormatはDateFormatを継承し、DateFormatでprotectedプロパティのCalendarクラスのオブジェクト:calendarを定義します.ただし、Calendarの複雑な概念のため、タイムゾーンやローカリゼーションなどに関連し、Jdkの実装ではメンバー変数を使用してパラメータを伝達するため、マルチスレッドの場合にエラーが発生します.
formatメソッドには、次のコードがあります.
 private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

    boolean useDateFormatSymbols = useDateFormatSymbols();

        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
        int count = compiledPattern[i++] & 0xff;
        if (count == 255) {
        count = compiledPattern[i++] << 16;
        count |= compiledPattern[i++];
        }

        switch (tag) {
        case TAG_QUOTE_ASCII_CHAR:
        toAppendTo.append((char)count);
        break;

        case TAG_QUOTE_CHARS:
        toAppendTo.append(compiledPattern, i, count);
        i += count;
        break;

        default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
        break;
        }
    }
        return toAppendTo;
    }

 
  calendar.setTime(date)この文はcalendarを変更し、後で、calendarはまた(subFormatメソッドで)これが問題を引き起こす根源です.想像してみてください.1つのマルチスレッド環境では、2つのスレッドが同じSimpleDateFormatのインスタンスを持ち、それぞれformatメソッドを呼び出します.スレッド1はformatメソッドを呼び出し、calendarという文字セグメントを変更します.中断しました.スレッド2は実行を開始し、calendarも変更しました.また途切れた.スレッド1が戻ってきたとき、calendarはすでに設定された値ではなく、スレッド2設計の道を歩んでいた.複数のスレッドがcalendarオブジェクトを同時に争うと、時間の違い、スレッドの停止など、さまざまな問題が発生します.formatの実装を分析すると、メンバー変数calendarを使用する唯一の利点は、subFormatを呼び出すときにパラメータが1つ足りないのに、多くの問題をもたらすことです.実は、ここで局所変数を使って、一緒に伝えれば、すべての問題が解決されます.この問題の背後には、ステータスなし:ステータスレスメソッドのメリットの1つとして、さまざまな環境で安全に呼び出すことができるというより重要な問題が隠されています.メソッドがステータスであるかどうかを測定するには、インスタンスのフィールドなどのグローバル変数などの他のものを変更したかどうかを見ます.formatメソッドは実行中にSimpleDateFormatのcalendarフィールドを変更したので、ステータスがあります.
これは、システムの開発と設計において、次の3つの点に注意することを同時に示しています.
  1.自分で共通クラスを書くときは、マルチスレッド呼び出しの場合の結果をコメントで明確に説明します
  2.スレッド環境では、共有する変数ごとにスレッドのセキュリティに注意してください.
  3.私たちのクラスと方法は設計をする時、できるだけ無状態に設計しなければならない.
三.解決策
  1.必要に応じて新しいインスタンスを作成します.
 
package com.peidasoft.dateformat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {
    
    public static  String formatDate(Date date)throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }
    
    public static Date parse(String strDate) throws ParseException{
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    }
}

 
説明:SimpleDateFormatを使用する必要がある場所にインスタンスを新規作成すると、スレッドセキュリティの問題があるオブジェクトを共有からローカルプライベートに変更してもマルチスレッドの問題は回避できますが、オブジェクトの作成に負担がかかります.一般的には、パフォーマンスへの影響比は明らかではありません.
  2.同期の使用:SimpleDateFormatオブジェクトの同期
 
package com.peidasoft.dateformat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateSyncUtil {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      
    public static String formatDate(Date date)throws ParseException{
        synchronized(sdf){
            return sdf.format(date);
        }  
    }
    
    public static Date parse(String strDate) throws ParseException{
        synchronized(sdf){
            return sdf.parse(strDate);
        }
    } 
}

 
説明:スレッドが多い場合、1つのスレッドがメソッドを呼び出すと、他のメソッドを呼び出すスレッドがblockされ、マルチスレッドの同時量が大きい場合はパフォーマンスに影響します.
  3.ThreadLocalの使用:
 
package com.peidasoft.dateformat;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ConcurrentDateUtil {

    private static ThreadLocal threadLocal = new ThreadLocal() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    }

    public static String format(Date date) {
        return threadLocal.get().format(date);
    }
}

 
もう1つの書き方:
package com.peidasoft.dateformat;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadLocalDateUtil {
    private static final String date_format = "yyyy-MM-dd HH:mm:ss";
    private static ThreadLocal threadLocal = new ThreadLocal(); 
public static DateFormat getDateFormat()   
    {  
        DateFormat df = threadLocal.get();  
        if(df==null){  
            df = new SimpleDateFormat(date_format);  
            threadLocal.set(df);  
        }  
        return df;  
    }  

    public static String formatDate(Date date) throws ParseException {
        return getDateFormat().format(date);
    }

    public static Date parse(String strDate) throws ParseException {
        return getDateFormat().parse(strDate);
    }   
}