158 votes

"Java DateFormat n'est pas thread-safe" qu'est-ce entraîne?

Tout le monde met en garde concernant Java DateFormat pas être thread-safe et je comprends le concept théorique.

Mais je ne suis pas en mesure de visualiser ce qu'problèmes réels que nous pouvons faire face à cause de cela. Dites, j'ai un DateFormat champ dans une classe et le même qui est utilisé dans différentes méthodes dans la classe (le formatage des dates) dans un environnement multi-thread.

Sera-ce la cause:

  • toute exception comme format d'exception
  • l'incohérence dans les données
  • toute autre question?

Aussi, veuillez expliquer pourquoi.

280voto

dogbane Points 85749

Nous allons l'essayer.

Voici un programme dans lequel plusieurs threads utilisation partagée SimpleDateFormat.

Programme:

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());
    }
}

Exécuter cette opération plusieurs fois et vous verrez:

Exceptions:

Voici quelques exemples:

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)

3.

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)

Des Résultats Incorrects:

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

Des Résultats Corrects:

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

Une autre approche consiste à utiliser en toute sécurité DateFormats dans un environnement multi-thread est d'utiliser un ThreadLocal variable pour contenir l' DateFormat objet, ce qui signifie que chaque thread a son propre exemplaire et n'a pas besoin d'attendre que les autres threads pour le libérer. C'est de cette façon:

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;
  }
}

Voici un bon poste avec plus de détails.

31voto

Jon Skeet Points 692016

Je m'attends à ce que la corruption de données - par exemple, si vous êtes à l'analyse de deux dates dans le même temps, vous pourriez avoir un appel polluées par des données provenant d'une autre.

Il est facile d'imaginer comment cela pourrait se produire: l'analyse implique souvent le maintien d'un certain montant d'état pour ce que vous avez lu jusqu'à présent. Si les deux fils sont à la fois le piétinement sur le même état, vous aurez des problèmes. Par exemple, DateFormat expose calendar champ de type Calendar, et en regardant le code de l' SimpleDateFormat, certaines méthodes s'appellent calendar.set(...) et que d'autres appellent calendar.get(...). Ce n'est clairement pas thread-safe.

Je n'ai pas regardé l' exacte détails de pourquoi DateFormat n'est pas thread-safe, mais pour moi, c'est assez pour savoir que c' est dangereux, sans synchronisation exacte des mœurs de la non-sécurité pourrait même changer entre les versions.

Personnellement, je voudrais utiliser les analyseurs de Joda Time au lieu de cela, car ils sont thread-safe - et Joda Time est une bien meilleure date et l'heure de l'API pour commencer :)

10voto

Bozho Points 273663

En gros, que vous ne devrait pas définir un DateFormat comme variable d'instance d'un objet accessible par de nombreux threads, ou static.

Les formats de Date ne sont pas synchronisés. Il est recommandé de créer des format instances pour chaque thread.

Ainsi, dans le cas où votre Foo.handleBar(..) est accessible par plusieurs threads, au lieu de:

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

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

vous devez utiliser:

public class Foo {

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

Aussi, dans tous les cas, n'ont pas d' static DateFormat

Comme indiqué par Jon Skeet, vous pouvez avoir à la fois statique et un partage de variables d'instance dans le cas où vous effectuez la synchronisation externe (c'est à dire utiliser synchronized autour des appels à l' DateFormat)

2voto

Jigar Joshi Points 116533

Les formats de Date ne sont pas synchronisés. Il est recommandé de créer des format instances pour chaque thread. Si plusieurs threads accèdent à un format en même temps, elle doit être synchronisée à l'externe.

Cela signifie que vous avez un objet de DateFormat et vous accédez à un même objet à partir de deux threads différents et vous appelez format méthode sur cet objet à la fois thread va entrer sur la même méthode, en même temps, sur le même objet, de sorte que vous pouvez visualiser, il ne se traduira pas par bon résultat

Si vous avez à travailler avec DateFormat importe comment, alors vous devriez faire quelque chose

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

1voto

Michał Niklas Points 15907

Les données sont endommagées. Hier, j'ai remarqué dans mon programme multithread où j'ai eu statique DateFormat objet et appelé ses format() pour les valeurs lues via JDBC. J'avais instruction SQL select où j'ai lu la même date, avec des noms différents (SELECT date_from, date_from AS date_from1 ...). De telles déclarations ont été à l'aide de 5 threads pour les différentes dates en WHERE clasue. Dates paraissait "normal", mais ils diffèrent en valeur, tandis que toutes les dates ont été de la même année, le mois et le jour a changé.

D'autres réponses vous montrer le chemin pour éviter une telle corruption. J'ai fait mon DateFormat pas statique, il est maintenant un membre d'une classe qui appelle les instructions SQL. J'ai testé aussi la version statique de la synchronisation. À la fois bien travaillé avec aucune différence dans les performances.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X