2009-12-22 4 views
1

Cette question concerne les retombées de l'utilisation de SingleThreadExecutor (JDK 1.6). Des questions connexes ont déjà été posées et répondues dans ce forum, mais je crois que la situation à laquelle je suis confrontée est un peu différente.Multiple SingleThreadExecutors pour une application donnée ... une bonne idée?

Divers composants de l'application (appelons les composants C1, C2, C3 etc.) génèrent des messages (sortants), principalement en réponse à des messages (entrants) qu'ils reçoivent d'autres composants. Ces messages sortants sont conservés dans des files d'attente qui sont généralement ArrayBlockingQueue instances - pratique assez standard peut-être. Toutefois, les messages sortants doivent être traités dans l'ordre dans lequel ils ont été ajoutés. Je suppose que l'utilisation d'un SingleThreadExector est la réponse évidente ici. Nous finissons par avoir une situation 1: 1 - unSingleThreadExecutor pour une file d'attente (qui est dédiée aux messages émanant de un composant).

Maintenant, le nombre de composants (C1, C2, C3 ...) est inconnu à un moment donné. Ils viendront à l'existence en fonction des besoins des utilisateurs (et seront également éliminés). Nous parlons de 200 à 300 de ces composants à la charge de pointe. Suivant le principe de conception 1: 1 énoncé ci-dessus, nous allons organiser pour 200 SingleThreadExecutor s. C'est la source de ma requête ici.

Je ne suis pas à l'aise avec la pensée de devoir créer autant de SingleThreadExecutor s. Je préfèrerais essayer d'utiliser un pool de SingleThreadExecutor s, si cela est logique et plausible (tout cours/modèle prêt à l'emploi, vu avant). J'ai lu beaucoup de messages sur l'utilisation recommandée de SingleThreadExecutor ici, mais qu'en est-il une piscine de la même chose?

Qu'en pensent les femmes et les hommes érudits? Je voudrais être dirigé, corrigé ou simplement, admonesté :-).

+0

La commande est-elle globale ou par composant? – cletus

+0

Bonne question et désolé, je n'ai pas précisé cela. L'exigence de commande est par composant. –

Répondre

1

Si votre exigence est que les messages soient traités dans l'ordre où ils sont postés, alors vous en voulez un et un seul SingleThreadExecutor. Si vous avez plusieurs exécuteurs, les messages seront traités dans le désordre dans l'ensemble des exécuteurs.

Si les messages ne doivent être traités que dans l'ordre où ils sont reçus pour un seul producteur, il est donc logique d'avoir un exécuteur par producteur. Si vous essayez de mettre en commun des exécuteurs, vous devrez consacrer beaucoup de temps à établir des liens entre le producteur et l'exécuteur.

Puisque vous indiquez que vos producteurs auront des durées de vie définies, une chose que vous devez vous assurer est que vous éteignez correctement vos exécuteurs quand ils ont terminé.

+0

Merci. Cette part de travail dans votre réponse a été la source de ma déconfiture. Même si je parviens à le faire, avoir 200 SingleThreadPoolExecutors n'est peut-être pas une bonne idée. Qu'est-ce que tu penses? –

+0

Mon commentaire "beaucoup de travail" était en fait une suggestion que les 200 exécuteurs sont la bonne façon de procéder ... à condition de ne pas pouvoir utiliser un seul exécuteur. Mais vous seul pouvez connaître les exigences réelles. – kdgregory

0

Les tâches de messagerie et de traitement par lots ont été résolues à maintes reprises. Je suggère de ne pas essayer de le résoudre à nouveau. Au lieu de cela, regardez dans Quartz, qui gère les pools de threads, les tâches persistantes dans une base de données, etc. Ou, peut-être même mieux se pencher sur JMS/ActiveMQ. Mais, à tout le moins, se pencher sur Quartz, si vous ne l'avez pas déjà fait. Oh, et Spring rend le travail avec Quartz tellement plus facile ...

+0

Quartz vaut certainement le coup d'oeil. Merci. JMS est trop lourd pour le type d'application que je suis impliqué; Les caractéristiques de déploiement de l'application sont dissuasives à l'utilisation de Spring. –

0

Je n'y vois aucun problème. Essentiellement, vous avez des files d'attente indépendantes et chacune doit être drainée séquentiellement, un fil pour chacun est un design naturel. Tout ce que vous pouvez imaginer est essentiellement le même. À titre d'exemple, lorsque Java NIO est apparu pour la première fois, des frameworks ont été écrits essayant d'en tirer profit et de s'éloigner du modèle thread-per-request. À la fin, certains auteurs ont admis que pour fournir un bon modèle de programmation, ils réimplémentaient tout simplement le filetage.

+0

Merci, mais est-ce un bon design pour avoir un tas de SingleThreadPoolExecutors (à hauteur de 300 à l'heure de pointe) - c'est la question. Cet arrangement me permet certainement de rester simple, mais l'approuverais-je du point de vue performance/scalabilité (éventuel)? Un grand merci pour prendre le temps de répondre. –

+0

200 threads ne devrait pas être un problème pour une seule machine. Si vous voulez une solution plus robuste et évolutive, vous avez besoin d'une architecture distribuée, les frameworks existants à ces fins doivent être examinés. – irreputable

0

Il est impossible de dire si des threads de 300 ou même de 3 000 entraîneront des problèmes sans en savoir plus sur votre application. Je vous recommande fortement de profiler votre application avant d'ajouter plus de complexité

La première chose à vérifier est que le nombre de threads en cours d'exécution ne doit pas être supérieur au nombre de cœurs disponibles pour exécuter ces threads. Plus vous avez de threads actifs, plus vous perdez de temps à gérer ces threads (le changement de contexte est coûteux) et moins vous travaillez.

Le moyen le plus simple de limiter le nombre de threads en cours d'exécution est d'utiliser le sémaphore. Acquérir le sémaphore avant de commencer le travail et le relâcher après le travail.

Malheureusement, limiter le nombre de threads en cours d'exécution peut ne pas être suffisant. Bien que cela puisse aider, les frais généraux peuvent encore être élevés, si le temps passé par changement de contexte est une partie importante du coût total d'une unité de travail. Dans ce scénario, le moyen le plus efficace consiste souvent à avoir un nombre fixe de files d'attente. Vous obtenez la file d'attente à partir du pool global de files d'attente lorsque le composant s'initialise à l'aide d'un algorithme tel que round-robin pour la sélection de la file d'attente. Si vous êtes dans un de ces cas malheureux où les solutions les plus évidentes ne fonctionnent pas, je commencerais par quelque chose de relativement simple: un pool de threads, une file concurrente, un verrou, une liste de files d'attente et une queue temporaire pour chaque thread dans le pool .

Publier du travail dans la file d'attente est simple: ajouter la charge utile et l'identité du producteur.

Le traitement est également relativement simple. D'abord vous obtenez l'article suivant de la file d'attente. Ensuite, vous acquérez le verrou. Pendant que vous avez un verrou en place, vous vérifiez si d'autres threads exécutent une tâche pour le même producteur. Sinon, vous enregistrez le thread en ajoutant une file d'attente temporaire à la liste des files d'attente. Sinon, vous ajoutez une tâche à une file d'attente temporaire existante. Enfin, vous libérez le verrou. Maintenant vous exécutez la tâche ou interrogez pour next et recommencez selon que le thread actuel a été enregistré pour exécuter des tâches. Après l'exécution de la tâche, vous obtenez à nouveau le verrou et voyez s'il y a plus de travail à faire dans la file d'attente temporaire. Sinon, supprimez la file d'attente de la liste. Sinon, passez à la prochaine tâche. Enfin, vous libérez le verrou. Encore une fois, vous choisissez d'exécuter la tâche ou de recommencer.