45 votes

Frais généraux de création de threads Java

La sagesse conventionnelle nous dit que les applications java d'entreprise à fort volume doivent utiliser le pooling de threads plutôt que de créer de nouveaux threads de travail. L'utilisation de java.util.concurrent permet de simplifier les choses.

Il existe cependant des situations où le pooling de threads n'est pas une bonne solution. L'exemple spécifique avec lequel je me bats actuellement est l'utilisation de InheritableThreadLocal qui permet ThreadLocal pour qu'elles soient "transmises" à tous les threads créés. Ce mécanisme ne fonctionne pas lorsqu'on utilise des pools de threads, car les threads de travail ne sont généralement pas créés à partir du thread de la requête, mais sont préexistants.

Il existe des moyens de contourner ce problème (les locals du fil peuvent être explicitement transmis), mais ce n'est pas toujours approprié ou pratique. La solution la plus simple est de créer de nouveaux threads de travail à la demande, et de laisser InheritableThreadLocal faire son travail.

Cela nous ramène à la question suivante : si j'ai un site à fort volume, où les threads de requête des utilisateurs génèrent chacun une demi-douzaine de threads de travail (c'est-à-dire sans utiliser de pool de threads), cela va-t-il poser un problème à la JVM ? Nous parlons potentiellement de quelques centaines de nouveaux threads créés chaque seconde, chacun durant moins d'une seconde. Les JVM modernes optimisent-elles bien cela ? Je me souviens de l'époque où la mise en commun des objets était souhaitable en Java, car la création d'objets était coûteuse. Depuis, cela est devenu inutile. Je me demande s'il en va de même pour le pooling de threads.

Je l'évaluerais bien, si je savais ce qu'il faut mesurer, mais je crains que les problèmes soient plus subtils que ce que l'on peut mesurer avec un profileur.

Remarque : le bien-fondé de l'utilisation des localisateurs de fil n'est pas la question ici, alors ne me suggérez pas de ne pas les utiliser.

37voto

Jaan Points 591

Voici un exemple de testcase :

public class ThreadSpawningPerformanceTest {
static long test(final int threadCount, final int workAmountPerThread) throws InterruptedException {
    Thread[] tt = new Thread[threadCount];
    final int[] aa = new int[tt.length];
    System.out.print("Creating "+tt.length+" Thread objects... ");
    long t0 = System.nanoTime(), t00 = t0;
    for (int i = 0; i < tt.length; i++) { 
        final int j = i;
        tt[i] = new Thread() {
            public void run() {
                int k = j;
                for (int l = 0; l < workAmountPerThread; l++) {
                    k += k*k+l;
                }
                aa[j] = k;
            }
        };
    }
    System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms.");
    System.out.print("Starting "+tt.length+" threads with "+workAmountPerThread+" steps of work per thread... ");
    t0 = System.nanoTime();
    for (int i = 0; i < tt.length; i++) { 
        tt[i].start();
    }
    System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms.");
    System.out.print("Joining "+tt.length+" threads... ");
    t0 = System.nanoTime();
    for (int i = 0; i < tt.length; i++) { 
        tt[i].join();
    }
    System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms.");
    long totalTime = System.nanoTime()-t00;
    int checkSum = 0; //display checksum in order to give the JVM no chance to optimize out the contents of the run() method and possibly even thread creation
    for (int a : aa) {
        checkSum += a;
    }
    System.out.println("Checksum: "+checkSum);
    System.out.println("Total time: "+totalTime*1E-6+" ms");
    System.out.println();
    return totalTime;
}

public static void main(String[] kr) throws InterruptedException {
    int workAmount = 100000000;
    int[] threadCount = new int[]{1, 2, 10, 100, 1000, 10000, 100000};
    int trialCount = 2;
    long[][] time = new long[threadCount.length][trialCount];
    for (int j = 0; j < trialCount; j++) {
        for (int i = 0; i < threadCount.length; i++) {
            time[i][j] = test(threadCount[i], workAmount/threadCount[i]); 
        }
    }
    System.out.print("Number of threads ");
    for (long t : threadCount) {
        System.out.print("\t"+t);
    }
    System.out.println();
    for (int j = 0; j < trialCount; j++) {
        System.out.print((j+1)+". trial time (ms)");
        for (int i = 0; i < threadCount.length; i++) {
            System.out.print("\t"+Math.round(time[i][j]*1E-6));
        }
        System.out.println();
    }
}
}

Les résultats sur Windows 7 64 bits avec 32-bit Sun's Java 1.6.0_21 Client VM sur Intel Core2 Duo E6400 @2.13 GHz sont les suivants :

Number of threads  1    2    10   100  1000 10000 100000
1. trial time (ms) 346  181  179  191  286  1229  11308
2. trial time (ms) 346  181  187  189  281  1224  10651

Conclusions : Comme mon ordinateur a 2 cœurs, deux threads font le travail presque deux fois plus vite qu'un seul, comme prévu. Mon ordinateur peut générer près de 10000 threads par seconde, c'est à dire le coût de la création d'un fil est de 0,1 milliseconde. . Par conséquent, sur une telle machine, quelques centaines de nouveaux threads par seconde représentent une surcharge négligeable (comme on peut également le voir en comparant les chiffres des colonnes pour 2 et 100 threads).

9voto

Michael Borgwardt Points 181658

Tout d'abord, cela dépendra bien sûr beaucoup de la JVM que vous utilisez. Le système d'exploitation jouera également un rôle important. Supposons la JVM de Sun (Hm, l'appelle-t-on encore ainsi ?) :

L'un des principaux facteurs est la mémoire de la pile allouée à chaque thread, que vous pouvez régler à l'aide de la commande -Xssn Paramètre JVM - vous devez utiliser la valeur la plus faible possible.

Et ce n'est qu'une supposition, mais je pense que "deux cents nouveaux threads par seconde" est définitivement au-delà de ce que la JVM est conçue pour gérer confortablement. Je soupçonne qu'un simple benchmark révélera rapidement des problèmes assez peu subtils.

1voto

Bozho Points 273663
  • pour votre benchmark, vous pouvez utiliser JMeter + un profileur, qui devrait vous donner un aperçu direct du comportement dans un environnement aussi chargé. Laissez-le tourner pendant une heure et surveillez la mémoire, le processeur, etc. Si rien ne casse et que le(s) cpu ne surchauffe(nt) pas, c'est bon :)

  • peut-être pouvez-vous obtenir un pool de threads, ou personnaliser (étendre) celui que vous utilisez en ajoutant du code afin d'avoir le bon InheritableThreadLocal est fixé chaque fois qu'un Thread est acquis à partir du pool de threads. Chaque Thread a ces propriétés privées du paquet :

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.  
     */ 
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    Vous pouvez les utiliser (enfin, avec de la réflexion) en combinaison avec la fonction Thread.currentThread() pour obtenir le comportement souhaité. Cependant, c'est un peu improvisé, et de plus, je ne peux pas dire si cela (avec la réflexion) n'introduira pas une surcharge encore plus grande que la simple création des fils.

0voto

Terje Points 868

Je me demande s'il est nécessaire de créer de nouveaux fils de discussion pour chaque demande d'utilisateur si leur cycle de vie typique est aussi court qu'une seconde. Pourriez-vous utiliser une sorte de file d'attente Notify/Wait où vous créez un nombre donné de (daemon)threads, et ils attendent tous jusqu'à ce qu'il y ait une tâche à résoudre. Si la file d'attente devient longue, vous créez des threads supplémentaires, mais pas dans un rapport de 1 à 1. Cela sera probablement plus performant que de créer des centaines de nouveaux threads dont les cycles de vie sont si courts.

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