Maison  >  Article  >  Java  >  Résoudre la contradiction entre le mode singleton et la sécurité des threads sous Spring

Résoudre la contradiction entre le mode singleton et la sécurité des threads sous Spring

不言
不言avant
2018-10-22 17:30:084456parcourir

Le contenu de cet article vise à résoudre la contradiction entre le mode singleton et la sécurité des threads sous Spring. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Combien de personnes ne connaissent pas ou ignorent les problèmes de multi-threading lorsqu'elles utilisent le framework Spring ?

Parce que lors de l'écriture d'un programme ou de la réalisation de tests unitaires, il est difficile de rencontrer des problèmes multi-thread car il n'existe pas d'environnement capable de simuler facilement des tests multi-thread. Ensuite, lorsque plusieurs threads appellent le même bean, il y aura des problèmes de sécurité des threads. Si le mode de création du bean au Spring n'est pas singleton, il n'y aura pas de problème de ce type.

Mais si vous ne tenez pas compte des vulnérabilités potentielles, il deviendra un tueur caché du programme, éclatant lorsque vous ne le savez pas. De plus, il est généralement très difficile de le déclencher dans l'environnement de production lorsque le programme est livré pour utilisation.

Spring utilise ThreadLocal pour résoudre les problèmes de sécurité des threads

Nous savons qu'en général, seuls les beans sans état peuvent être partagés dans un environnement multithread. Au Spring, la plupart des beans peuvent être déclarés comme portée singleton. En effet, Spring utilise ThreadLocal pour traiter les états non thread-safe dans certains beans (tels que RequestContextHolder, TransactionSynchronizationManager, LocaleContextHolder, etc.), ce qui les rend thread-safe, car les beans avec état peuvent être partagés entre plusieurs threads.

Les applications Web générales sont divisées en trois niveaux : la couche de présentation, la couche de service et la couche de persistance. La logique correspondante est écrite dans différentes couches, et la couche inférieure ouvre les appels de fonction vers la couche supérieure via des interfaces. Dans des circonstances normales, tous les appels de programme, depuis la réception d'une requête jusqu'au retour d'une réponse, appartiennent au même thread.

ThreadLocal est une bonne idée pour résoudre les problèmes de sécurité des threads. Il résout le conflit d'accès simultané aux variables en fournissant une copie indépendante de la variable pour chaque thread. Dans de nombreux cas, ThreadLocal est plus simple et plus pratique que l'utilisation directe du mécanisme de synchronisation synchronisée pour résoudre les problèmes de sécurité des threads, et le programme résultant a une concurrence plus élevée.

S'il y a plusieurs threads exécutés en même temps dans le processus où se trouve votre code, ces threads peuvent exécuter ce code en même temps. Si les résultats de chaque exécution sont les mêmes que ceux des exécutions monothread et que les valeurs des autres variables sont les mêmes que celles attendues, cela est thread-safe. En d'autres termes : l'interface fournie par une classe ou un programme est une opération atomique pour les threads ou le basculement entre plusieurs threads ne provoquera pas d'ambiguïté dans les résultats d'exécution de l'interface, ce qui signifie que nous n'avons pas besoin de prendre en compte les problèmes de synchronisation. Les problèmes de sécurité des threads sont causés par des variables globales et des variables statiques.

S'il n'y a que des opérations de lecture et aucune opération d'écriture sur les variables globales et les variables statiques dans chaque thread, d'une manière générale, cette variable globale est thread-safe si plusieurs threads effectuent des opérations d'écriture en même temps ; , la synchronisation des threads doit généralement être prise en compte, sinon la sécurité des threads peut être affectée.
1) Les constantes sont toujours thread-safe car il n'y a que des opérations de lecture.
2) La création d'une nouvelle instance avant chaque appel de méthode est thread-safe car les ressources partagées ne seront pas accessibles.
3) Les variables locales sont thread-safe. Car à chaque fois qu'une méthode est exécutée, une variable locale sera créée dans un espace séparé, qui n'est pas une ressource partagée. Les variables locales incluent les variables de paramètres de méthode et les variables internes à la méthode.

Être avec état signifie avoir une fonction de stockage de données. Les objets avec état (Stateful Beans) sont des objets avec des variables d'instance qui peuvent enregistrer des données et ne sont pas thread-safe. Aucun état n'est conservé entre les appels de méthode.

Apatride est une opération unique et ne peut pas enregistrer de données. Un objet sans état (Stateless Bean) est un objet sans variables d'instance. Il ne peut pas enregistrer de données, c'est une classe immuable et il est thread-safe.

Objets avec état :

Les beans sans état sont adaptés au mode immuable. La technologie est en mode singleton, afin que les instances puissent être partagées et les performances améliorées. Les beans avec état ne sont pas sûrs dans un environnement multithread, le mode prototype Prototype est donc adapté. Prototype : Chaque demande de bean crée une nouvelle instance de bean.

L'implémentation par défaut de Struts2 est le mode Prototype. Autrement dit, une nouvelle instance d'action est générée pour chaque requête, il n'y a donc aucun problème de sécurité des threads. A noter que si Spring gère le cycle de vie de l'action, le scope doit être configuré en scope prototype

Thread safety case

SimpleDateFormat (ci-après dénommé sdf) classe interne Il existe une référence d'objet Calendar, qui est utilisée pour stocker les informations de date liées à ce sdf, telles que sdf.parse(dateStr), sdf.format(date) et ainsi de suite. etc. transmis par les paramètres de méthode sont tous des amis. Référence de calendrier à stocker. Cela posera un problème si votre sdf est statique, alors plusieurs threads partageront ce sdf, et en même temps partageront cette référence de calendrier, et observeront le. Méthode sdf.parse(), Vous retrouverez les appels suivants :

 Date parse() {
   calendar.clear(); // 清理calendar
   ... // 执行一些操作, 设置 calendar 的日期什么的
   calendar.getTime(); // 获取calendar的时间
 }

Le problème qui se posera ici est que si le thread A appelle sdf.parse(), et avant que Calendar.getTime() ne soit exécuté après Calendar.clear(), le thread B appelle à nouveau sdf .parse(), à ce moment-là, le thread B a également exécuté la méthode sdf.clear(), ce qui a entraîné l'effacement des données de calendrier du thread A (en fait, A et B ont été effacés en même temps ou lorsque A exécuté, il est suspendu après Calendar.clear). (). À ce moment-là, B commence à appeler sdf.parse() et se termine en douceur, de cette façon, la date stockée dans le calendrier de A devient la date du calendrier défini par B plus tard

Derrière ce problème se cache. un problème plus important est celui de l'apatridie : l'un des avantages de la méthode apatride est qu'elle peut être appelée en toute sécurité dans divers environnements. Pour mesurer si une méthode est avec état, cela dépend si elle modifie d'autres éléments, tels que les variables globales et les champs d'instance. La méthode format modifie le champ de calendrier de SimpleDateFormat pendant le processus en cours, il est donc avec état.

Cela nous rappelle également de prêter attention aux trois points suivants lors du développement et de la conception du système :

Lorsque vous écrivez vous-même des cours publics, vous devez clairement noter les conséquences des appels multithread dans les commentaires expliquer

Dans un environnement de thread, nous devons faire attention à la sécurité des threads de chaque variable partagée

Lors de la conception de nos classes et méthodes, nous devons faire de notre mieux pour les concevoir de manière transparente. Statut

Solution

1. Créez une nouvelle instance si nécessaire :

Remarque : utilisez SimpleDateFormat si nécessaire chaque fois que vous créez une nouvelle instance. Par exemple au même endroit, changer un objet présentant des problèmes de sécurité des threads de partagé à localement privé peut éviter les problèmes de multithread, mais cela augmente également la charge de création d'objets. Dans des circonstances normales, l’impact sur les performances n’est pas très évident.

2. Utiliser la synchronisation : synchroniser l'objet SimpleDateFormat

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

Description : Lorsqu'il y a plusieurs threads, lorsqu'un thread appelle cette méthode, d'autres threads veulent l'appeler. méthode Les threads doivent être bloqués. Lorsque la quantité de concurrence multithread est importante, cela aura un certain impact sur les performances.

3. Utilisez ThreadLocal :

public class ConcurrentDateUtil {
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @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);
    }
}

ou

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

Remarque : L'utilisation de ThreadLocal rend également les variables partagées exclusives. réduire le coût de création d'objets dans un environnement concurrent par rapport à l'exclusivité de la méthode. Si les exigences de performances sont relativement élevées, cette méthode est généralement recommandée.

4. Abandonnez le JDK et utilisez des classes de formatage de l'heure dans d'autres bibliothèques :

Utilisez FastDateFormat dans Apache commons, prétendant être SimpleDateFormat rapide et sécurisé pour les threads, malheureusement c'est possible. formate uniquement les dates et ne peut pas analyser les chaînes de date.

Utilisez la bibliothèque de classes Joda-Time pour gérer les problèmes liés au temps

Faites un test de stress simple. La première méthode est la plus lente et la troisième est la plus rapide. Mais même la méthode la plus lente est médiocre. performances. Pas mal, les méthodes générales 1 et 2 du système peuvent être satisfaites, il est donc difficile de devenir le goulot d'étranglement de votre système à ce stade. D'un point de vue simple, il est recommandé d'utiliser la première ou la deuxième méthode. Si vous souhaitez améliorer légèrement les performances lorsque cela est nécessaire, vous pouvez envisager d'utiliser la troisième méthode, en utilisant ThreadLocal pour la mise en cache.

La bibliothèque de classes Joda-Time est parfaite pour le traitement du temps et son utilisation est recommandée.

Résumé

Retour à la question du début de l'article : "Combien de personnes ne connaissent pas ou ignorent souvent les problèmes de multi-threading lors de l'utilisation du framework Spring ?" 》

En fait, n'importe qui peut écrire du code. Pourquoi l'effet du code écrit par l'architecte est-il si différent du vôtre ? Cela devrait être ce genre de petit problème auquel vous n'avez pas pensé mais que l'architecte a pris en compte.

Les architectes ont des connaissances plus larges, ont vu des situations plus spécifiques et ont une expérience plus riche dans la résolution de divers problèmes. Tant que vous développerez la pensée et les habitudes d’un architecte, serez-vous encore loin d’être un architecte ?


Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer