Analyse de l'exécution des tâches de la programmation d'applications simultanées Java

Aperçu

"Java Concurrent Programming Practice" écrit par Doug Lea et d'autres décrit la tâche de cette façon : "Dans la plupart des applications concurrentes, elles sont structurées autour de "l'exécution de tâches", et les tâches sont généralement des unités de travail abstraites et discrètes, en décomposant le travail de l'application. en plusieurs tâches, il peut simplifier l'organisation de l'application, fournir une frontière naturelle entre les transactions pour optimiser le processus de récupération des erreurs et fournir une structure de travail parallèle naturelle pour améliorer la concurrence. " La compréhension de ce passage est la suivante : Nous décomposons un processus très complexe. divisez le travail A en plusieurs petites tâches, puis laissez ces petites tâches commencer à faire leurs propres choses en même temps. Lorsque ces petites tâches seront terminées, elles seront fusionnées dans la tâche complexe finale A que nous souhaitons accomplir. Comment diviser raisonnablement cette tâche complexe A en petites tâches une par une, et comment organiser et exécuter ces tâches, et enfin obtenir efficacement les bons résultats, est devenu une question importante à considérer pour nous.

1. Division des limites d'exécution des tâches

Lorsque nous souhaitons accomplir une tâche complexe ou une tâche qui prend beaucoup de temps, nous la divisons souvent en plusieurs tâches à exécuter simultanément. Dans le cas le plus idéal, les tâches que nous divisons sont indépendantes les unes des autres : c'est-à-dire que les tâches divisées ne dépendent pas les unes des autres et que l'exécution d'une tâche ne dépend pas du statut et des résultats de calcul des autres tâches. L'indépendance entre les tâches facilite la concurrence. Si suffisamment de ressources processeur sont disponibles, des tâches indépendantes peuvent être exécutées en parallèle.

Sous une charge normale, notre application doit présenter un bon débit et une réactivité rapide. Les fournisseurs d'applications espèrent que le programme pourra prendre en charge autant d'utilisateurs que possible et réduire le coût du service pour chaque utilisateur, tandis que les utilisateurs espèrent obtenir une réponse du programme le plus rapidement possible. De plus, en cas de surcharge, les performances de l’application devraient diminuer progressivement et doucement, plutôt que de planter anormalement immédiatement. Pour atteindre cet objectif, nous devons être capables de tracer des limites claires entre les tâches et des stratégies d’exécution claires. Comme indiqué ci-dessous:

Insérer la description de l'image ici
Idéalement, les trois sous-tâches divisées sont indépendantes et ne dépendent pas les unes des autres, de sorte que nous puissions faire en sorte que les trois sous-tâches effectuent des opérations en même temps, de sorte que le fonctionnement de la tâche complexe A soit plus rapide. Les utilisateurs recevront également des réponses rapidement.

2. Idée et mise en œuvre d'une application serveur

La plupart des applications serveur offrent un moyen naturel de diviser les limites des tâches. La demande de chaque utilisateur est une limite, c'est-à-dire qu'une demande correspond à une tâche indépendante. Serveurs Web, serveurs de messagerie, serveurs de fichiers, conteneurs EJB et serveurs de bases de données, etc., ces serveurs peuvent tous accepter les demandes de connexion de clients distants via le réseau. Nous traitons chaque requête comme une tâche. Évidemment, ces tâches sont indépendantes. Par exemple, le résultat obtenu après la soumission d'un message au serveur de messagerie ne sera pas affecté par les autres messages en cours de traitement.

2.1 Exécution en série des tâches

Lors du développement d'une application, nous pouvons planifier des tâches grâce à diverses stratégies. La méthode la plus simple consiste à exécuter les tâches en série dans un seul thread. C'est-à-dire que les tâches doivent être exécutées une par une et en même temps, dans ce thread. une tâche est en cours d'exécution dans , le pseudo-code est le suivant :

public class WebServer {
    
    
    public static void main(String[] args) throws IOException {
    
    
        ServerSocket serverSocket = new ServerSocket(80);
        while (true){
    
    
            Socket connection = serverSocket.accept();
            handleRequest(connection);// 处理请求
        }
    }
}

Le code ci-dessus n'est pas disponible dans le commerce car il ne peut traiter qu'une seule requête à la fois. Lorsque d'autres requêtes arrivent, en supposant que le serveur traite la requête, vous devez attendre que le serveur ait traité la requête précédente. Supposons que chaque requête soit Si elle est ultra rapide, alors cette méthode est réalisable, mais dans le monde réel, les conditions du serveur sont en constante évolution. C'est donc ainsi 非常不推荐的.

Ce qui est encore plus inacceptable, c'est qu'on suppose que la requête Web de l'utilisateur contient un ensemble différent de calculs et d'opérations d'E/S. Le serveur doit attendre la fin des E/S et des opérations. Ces opérations peuvent être bloquées en raison d'une congestion du réseau ou de problèmes de connectivité. Dans un programme monothread, le blocage retardera non seulement l'achèvement de la requête en cours, mais empêchera également complètement le traitement de la requête en attente. Si la requête est bloquée pendant un certain temps longtemps, si c'est trop long, les utilisateurs penseront que le serveur est indisponible.

2.2 Créer un fil de discussion pour chaque demande d'exécution de tâches

Dans la section précédente, nous avons dit qu'il n'était pas très scientifique qu'un seul thread exécute les requêtes des utilisateurs, nous supposons donc qu'en créant un thread pour chaque requête pour l'exécuter, y aura-t-il des problèmes ? Avec cette question, analysons-la ensemble : le pseudo code est le suivant :

    public static void main(String[] args) throws IOException {
    
    
        ServerSocket serverSocket = new ServerSocket(80);
        while (true){
    
    
            Socket connection = serverSocket.accept();
            Runnable task = new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    handleRequest(connection);// 处理请求
                }
            };
            
            new Thread(task).start();
        }
    }

Comme le montre le code ci-dessus,nous créons un thread pour traiter chaque requête.Le processus de traitement des tâches est séparé du thread principal, permettant à la boucle principale d'attendre plus rapidement la prochaine connexion entrante. Cela permet au programme d'accepter de nouvelles demandes à l'avance avant de terminer la demande précédente, améliorant ainsi la réactivité du programme. Et plusieurs tâches peuvent être traitées en parallèle, c'est-à-dire que plusieurs demandes peuvent être traitées en même temps. S'il y a plusieurs processeurs ou si la tâche est bloquée pour une raison quelconque, comme attendre la fin des E/S, acquérir un verrou ou la disponibilité des ressources, etc., le débit du programme sera amélioré.Il convient de noter que lorsque plusieurs threads sont traités en parallèle, la sécurité des threads doit être prêtée attention.

假设在正常的负载下,为每个请求任务分配一个线程的方法能提升程序串行执行的性能,只要是请求到达速率不超出服务器的请求处理能力,那么这种方法可以同时带来更快的响应性和更高的吞吐率。

Alors dans un environnement de production, attribuer un thread à chaque tâche est définitivement une bonne chose ? Bien sûr que non, surtout lorsque l'on souhaite créer un grand nombre de threads, les principaux inconvénients sont les suivants :

(1) La surcharge du cycle de vie des threads est très élevée.
La création et la destruction des threads ne sont pas gratuites. Selon la plateforme, la surcharge réelle sera différente. La création et la destruction des threads prennent du temps
(2) Création de threads Nécessite une consommation de ressources.
Les threads actifs consomment des ressources, en particulier de la mémoire. Si le nombre de threads exécutables dépasse le nombre de processeurs disponibles, certains threads n'auront rien à faire et les ressources système seront gaspillées. Faites pression sur le éboueur. Au contraire, si vous disposez déjà de suffisamment de threads pour occuper tous les processeurs, la création de threads supplémentaires réduira en fait les performances.
(3) Stabilité
Il existe une limite au nombre de threads pouvant être créés. Cette limite varie d'une plate-forme à l'autre et est soumise à plusieurs facteurs, notamment les paramètres de démarrage de la JVM, la taille de la pile de requêtes dans le constructeur Thread et les restrictions de thread du système d'exploitation sous-jacent. Si ces limites ne sont pas respectées, une exception OutOfMemoryError est susceptible d'être levée.

Donc pour résumer, dans une certaine plage, l'ajout de threads peut améliorer le débit du système. Cependant, s'il dépasse cette plage, la création de plus de threads réduira le taux d'exécution du programme et créera trop de threads, alors l'ensemble de l'application va planter, et pour éviter ce danger, il devrait y avoir une limite sur le nombre de threads que l'application peut créer. Cela garantit que le programme ne manque pas de ressources lorsque la limite de threads est atteinte.

おすすめ

転載: blog.csdn.net/zxj2589/article/details/131450123