[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[jfriends-ml 10827] Re: DecimalFormat とか もスレッドセーフではない?



 井上泰です。

DateFormatがスレッドセーフかどうか?は未確認です。
曖昧な発言で申し訳ありません。

DDJ 7月号の記事をもとに作成したThreadLocalのコードを送ります。
問題があれば指摘してください。

秋元さんの資料、参考になります。確認します。

>http://www.geocities.co.jp/Playtown/1245/java/unsafe_simple_date_format.html
>  
>

-------------------------------------------
package common.util;

import java.util.*;
import java.text.*;
import java.io.*;

/**
* 日付フォーマット<br>
* <dl>
* <dt>動機</dt>
* <dd>SimpleDateFormatはスレッド・セーフではない。
* 複数のスレッドが同時にSimpleDateForamtのインスタンスを使用すると、
* ParseExceptionまたはNumberFormatExceptionを発生させることがある。
* 同期化のコストなしに複数スレッドでSimpleDateForamtを利用したい。</dt>
* <dt>解決</dt>
* <dd>ThreadLocalにSimpleDateForamtを格納する。
* SimpleDateForamtはスレッドごとにローカルなインスタンス変数になる。
* このため、同期化せずにインスタンスを利用することができる。</dt>
* </dl>
* @author Yasushi Inoue
* @version 1.0
*/
public class DateFormatUtils {

// static finalにしないとメモリリークというニュアンスのことがDDJ-7にあった
// WeakReferenceを使用しているため参照なき場合は解放されるのでは?
// フォーマットごとにThreadLocalのインスタンスを作成したが、一つでかまわない

/** yyyyMMdd スレッド固有の日付フォーマットクラス */
private static final ThreadLocal YYYYMMDD = new ThreadLocal() {
protected Object initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
/** yy/MM/dd スレッド固有の日付フォーマットクラス */
private static final ThreadLocal YY_MM_DD = new ThreadLocal() {
protected Object initialValue() {
return new SimpleDateFormat("yy/MM/dd");
}
};
/** yyyy/MM/dd スレッド固有の日付フォーマットクラス */
private static final ThreadLocal YYYY_MM_DD = new ThreadLocal() {
protected Object initialValue() {
return new SimpleDateFormat("yyyy/MM/dd");
}
};

/**
* 文字列yyyymmddを取得し、文字列(yy/MM/dd)を作成する<br>
* @param inYmd 年月日
* @return 文字列(yy/MM/dd)
* @exception ApplException 日付フォーマットエラー
*/
public static final String y4md2y_m_d(final String inYmd) throws
ApplException {
if (inYmd == null) {
throw new IllegalArgumentException();
}
String result = null;
try {
DateFormat df = ((DateFormat) YYYYMMDD.get());
// 厳密な日付チェックを行う
df.setLenient(false);
Date dt = df.parse(inYmd);
result = ((DateFormat) YY_MM_DD.get()).format(dt);
} catch (ParseException pex) {
userLog.error(pex.getMessage());
throw new ApplException(pex);
} catch (IllegalArgumentException iex) {
userLog.error(iex.getMessage());
throw new ApplException(iex);
}
return result;
}

private static final String VM_VERSION =
System.getProperties().getProperty("java.vm.version");

/**
* 文字列yy/MM/ddまたはyyyy/MM/ddを取得し文字列(yyyyMMdd)を作成する<br>
* 2桁の年は、年ウィンドウ2000年-2099年の下二桁とみなす。
* @param inYmd 年/月/日
* @return 文字列(yyyyMMdd)
* @exception ApplException 日付フォーマットエラー
*/
public static final String y_m_d2y4md(String inYmd) throws ApplException {

if (inYmd == null) {
return "";
}

String result = null;

try {

// DateFormat.setLenient(true)の場合、ヒューリスティックな変換がなされる。
// 1) 西暦00年は01に変換される。
// 2) たとえば、2003/01/32 -> 2003/02/01 など。
// DateFormat.setLenient(false)の場合、厳密な日付チェックが行われる。
// 1) 西暦00年は許されない。
// 2) たとえば、2003/02/29 は許されない。
if (VM_VERSION.startsWith("1.4")) {
// 年が0かどうかのチェック。j2se1.4以降。これより前はコンパイルエラー
if (inYmd.matches("^(0)+\\/.*")) {
inYmd = inYmd.replaceFirst("^(0)+","2000");
}
} else {
// 年が0かどうかのチェック。j2se1.4以前
int pos = inYmd.indexOf('/');
if (Integer.valueOf(inYmd.substring(0,pos)).intValue() == 0) {
inYmd = "2000" + inYmd.substring(pos);
}
}

Calendar cl = Calendar.getInstance();
DateFormat df = (DateFormat) YYYY_MM_DD.get();
// 厳密な日付チェックを行う
df.setLenient(false);
cl.setTime(df.parse(inYmd));

// 年をチェックする
int year = cl.get(Calendar.YEAR);
if (year < 100) {
// 2桁年(2000年以降)とみなす
cl.set(
// 西暦2000年以降に補正する
(year+2000)
,cl.get(Calendar.MONTH)
,cl.get(Calendar.DATE)
);
} else if (year < 1900) {
// 1900年より前は不正入力とみなす
throw new ApplException();
}

result = ((DateFormat) YYYYMMDD.get()).format(cl.getTime());

} catch (NumberFormatException nex) {
// Integer#valueOf(String)の失敗
userLog.error(nex.getMessage());
throw new ApplException(nex);
} catch (ArrayIndexOutOfBoundsException aex) {
// String#indexOf('/')の失敗
userLog.error(aex.getMessage());
throw new ApplException(aex);
} catch (ParseException pex) {
// SimpleDateFormat#parse(String)の失敗
userLog.error(pex.getMessage());
throw new ApplException(pex);
} catch (IllegalArgumentException iex) {
userLog.error(iex.getMessage());
throw new ApplException(iex);
}

return result;

}

public static void main(String[] argv) throws Exception {
// テスト
System.out.println(y4md2y_m_d("20030101"));
System.out.println(y4md2y_m_d("20031231"));

System.out.println(y_m_d2y4md(y4md2y_m_d("20030101")));
System.out.println(y_m_d2y4md(y4md2y_m_d("20031231")));

System.out.println(y_m_d2y4md("2000/01/01"));
System.out.println(y_m_d2y4md("2001/1/1"));

/** 00 のとき 01になる.補正済み */
System.out.println(y_m_d2y4md("00/12/1"));

System.out.println(y_m_d2y4md("01/12/1"));
System.out.println(y_m_d2y4md("02/12/1"));
System.out.println(y_m_d2y4md("03/1/1"));

/* テスト結果
03/01/01
03/12/31
20030101
20031231
20000101
20010101
20001201
20011201
20021201
20030101
*/

System.out.println(y_m_d2y4md("03/1/50"));
}

}

class ApplException extends Exception {

public ApplException() {
super();
}

public ApplException(String msg) {
super(msg);
}

public ApplException(Throwable th) {
super(th);
}

/**
* スタックトレースの取得<br>
* 例外のスタックトレースをStringにセットして返す
* @param th Throwable 例外
* @return String スタックトレース
*/
public static final String throwableToString(Throwable th) {
if (th == null) {
return "";
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter( sw );
th.printStackTrace( pw );
pw.close();
return sw.getBuffer().toString();
}

}

class userLog {

public static void error(String msg) {
System.out.println(msg);
}

public static void error(String msg, Throwable th) {
System.out.println(msg + " : " + ApplException.throwableToString(th));
}

}