[java] “Java DateFormat은 스레드 안전하지 않습니다”이것이 무엇을 야기합니까?

모든 사람들은 스레드가 안전하지 않은 Java DateFormat에 대해주의하고 이론적으로 개념을 이해합니다.

그러나 이로 인해 우리가 직면 할 수있는 실제 문제를 시각화 할 수 없습니다. 예를 들어, 클래스에 DateFormat 필드가 있고 멀티 스레드 환경에서 클래스의 다른 메소드 (서식 날짜)에 동일하게 사용됩니다.

이것이 원인입니까?

  • 형식 예외와 같은 예외
  • 데이터 불일치
  • 다른 문제?

또한 이유를 설명하십시오.



답변

사용해 봅시다.

다음은 여러 스레드가 공유를 사용하는 프로그램입니다 SimpleDateFormat.

프로그램 :

public static void main(String[] args) throws Exception {

    final DateFormat format = new SimpleDateFormat("yyyyMMdd");

    Callable<Date> task = new Callable<Date>(){
        public Date call() throws Exception {
            return format.parse("20101022");
        }
    };

    //pool with 5 threads
    ExecutorService exec = Executors.newFixedThreadPool(5);
    List<Future<Date>> results = new ArrayList<Future<Date>>();

    //perform 10 date conversions
    for(int i = 0 ; i < 10 ; i++){
        results.add(exec.submit(task));
    }
    exec.shutdown();

    //look at the results
    for(Future<Date> result : results){
        System.out.println(result.get());
    }
}

이것을 몇 번 실행하면 다음을 볼 수 있습니다.

예외 :

다음은 몇 가지 예입니다.

1.

Caused by: java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
    at java.lang.Long.parseLong(Long.java:431)
    at java.lang.Long.parseLong(Long.java:468)
    at java.text.DigitList.getLong(DigitList.java:177)
    at java.text.DecimalFormat.parse(DecimalFormat.java:1298)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)

2.

Caused by: java.lang.NumberFormatException: For input string: ".10201E.102014E4"
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
    at java.lang.Double.parseDouble(Double.java:510)
    at java.text.DigitList.getDouble(DigitList.java:151)
    at java.text.DecimalFormat.parse(DecimalFormat.java:1303)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)

삼.

Caused by: java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084)
    at java.lang.Double.parseDouble(Double.java:510)
    at java.text.DigitList.getDouble(DigitList.java:151)
    at java.text.DecimalFormat.parse(DecimalFormat.java:1303)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)

잘못된 결과 :

Sat Oct 22 00:00:00 BST 2011
Thu Jan 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Thu Oct 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010

올바른 결과 :

Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010

다중 스레드 환경에서 DateFormats를 안전하게 사용하는 또 다른 방법은 ThreadLocal변수 를 사용 하여 DateFormat 객체 를 보유하는 것입니다. 즉, 각 스레드에는 고유 한 사본이 있으며 다른 스레드가이를 해제 할 때까지 기다릴 필요가 없습니다. 방법은 다음과 같습니다.

public class DateFormatTest {

  private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyyMMdd");
    }
  };

  public Date convert(String source) throws ParseException{
    Date d = df.get().parse(source);
    return d;
  }
}

자세한 내용이 담긴 좋은 게시물 입니다.


답변

데이터가 손상 될 수 있습니다. 예를 들어 두 날짜를 동시에 구문 분석하는 경우 한 통화가 다른 통화의 데이터로 오염 될 수 있습니다.

구문 분석에는 종종 지금까지 읽은 내용에 대해 일정량의 상태를 유지하는 것이 포함됩니다. 두 스레드가 모두 같은 상태에서 짓밟 히면 문제가 발생합니다. 예를 들어, 유형 DateFormatcalendar필드를 노출하고 의 메소드를 Calendar보고 SimpleDateFormat일부 메소드는 호출 calendar.set(...)하고 다른 메소드는 호출 calendar.get(...)합니다. 이것은 스레드로부터 안전하지 않습니다.

나는이에보고하지 않은 정확한 이유에 대한 자세한 DateFormat스레드로부터 안전하지 않습니다,하지만 나를 위해 그것이 알고 충분 하다 동기화없이 안전 – 비 안전의 정확한 매너도 릴리스간에 변경 될 수 있습니다.

개인적으로 난에서 파서 사용하는 것이 Joda 시간을 가로, 대신 있습니다 스레드 안전 – 그리고 Joda 시간에 시작하는 더 나은 날짜와 시간 API입니다 🙂


답변

Java 8을 사용하는 경우을 사용할 수 있습니다 DateTimeFormatter.

패턴에서 작성된 포맷터는 필요한만큼 여러 번 사용할 수 있으며 변경할 수 없으며 스레드로부터 안전합니다.

암호:

LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String text = date.format(formatter);
System.out.println(text);

산출:

2017-04-17


답변

대략 DateFormat많은 스레드가 액세스하는 객체의 인스턴스 변수로 정의해서는 안됩니다 static.

날짜 형식이 동기화되지 않았습니다. 각 스레드마다 별도의 형식 인스턴스를 작성하는 것이 좋습니다.

따라서 Foo.handleBar(..)여러 스레드에서 액세스하는 경우 대신 다음을 수행하십시오.

public class Foo {
    private DateFormat df = new SimpleDateFormat("dd/mm/yyyy");

    public void handleBar(Bar bar) {
        bar.setFormattedDate(df.format(bar.getStringDate());
    }
}

당신은 사용해야합니다 :

public class Foo {

    public void handleBar(Bar bar) {
        DateFormat df = new SimpleDateFormat("dd/mm/yyyy");
        bar.setFormattedDate(df.format(bar.getStringDate());
    }
}

또한 모든 경우에 static DateFormat

존 소총에 의해 언급 한 바와 같이, 당신은 정적 및 사례의 공유 인스턴스 변수는 외부 동기화 (즉, 사용 수행을 모두 할 수 있습니다 synchronized받는 전화의 주위를 DateFormat)


답변

날짜 형식이 동기화되지 않았습니다. 각 스레드마다 별도의 형식 인스턴스를 작성하는 것이 좋습니다. 여러 스레드가 동시에 형식에 액세스하는 경우 외부에서 동기화해야합니다.

즉, DateFormat의 객체가 있고 두 개의 다른 스레드에서 동일한 객체에 액세스하고 해당 객체에서 format 메소드를 호출한다고 가정하면 두 스레드가 동일한 객체에서 동일한 메소드에 동시에 입력되어 원화를 시각화 할 수 있습니다 적절한 결과를 얻지 못함

DateFormat으로 작업 해야하는 경우 어떻게해야합니까?

public synchronized myFormat(){
// call here actual format method
}

답변

데이터가 손상되었습니다. 어제 나는 정적 DateFormat객체가 있고 format()JDBC를 통해 읽은 값을 호출하는 멀티 스레드 프로그램에서 그것을 발견했습니다 . 다른 이름 ( SELECT date_from, date_from AS date_from1 ...)으로 동일한 날짜를 읽는 SQL select 문이 있습니다. 이러한 진술은 다양한 날짜에 5 개의 스레드로 사용되었습니다.WHERE . 날짜는 “정상”으로 보였지만 가치는 달랐습니다. 모든 날짜는 같은 해의 월과 일만 변경되었습니다.

다른 답변은 그러한 부패를 피하는 방법을 보여줍니다. 나는 DateFormat정적이 아니었고 이제는 SQL 문을 호출하는 클래스의 멤버입니다. 정적 버전도 동기화하여 테스트했습니다. 둘 다 성능의 차이없이 잘 작동했습니다.


답변

Format, NumberFormat, DateFormat, MessageFormat 등의 사양은 스레드로부터 안전하도록 설계되지 않았습니다. 또한 구문 분석 메소드는 메소드를 호출하며 Calendar.clone()캘린더 풋 프린트에 영향을 미치므로 많은 스레드가 동시에 구문 분석하면 캘린더 인스턴스의 복제가 변경됩니다.

자세한 내용은 thisthis 와 같은 버그 보고서 이며 DateFormat 스레드 안전 문제의 결과입니다.