Heim  >  Artikel  >  Java  >  Eine ausführliche Erläuterung der Verwendung und Prinzipien von Thread-Pools in Java

Eine ausführliche Erläuterung der Verwendung und Prinzipien von Thread-Pools in Java

黄舟
黄舟Original
2017-10-09 09:58:261479Durchsuche

Dieser Artikel stellt hauptsächlich relevante Informationen zur Verwendung und den Prinzipien des Java-Thread-Pools vor. Er hat einen gewissen Referenzwert.

Was ist ein Thread-Pool?

Wir können Java verwenden, um ganz einfach einen neuen Thread zu erstellen, und das Betriebssystem kostet auch viel Geld, um einen Thread zu erstellen. Basierend auf der Wiederverwendung von Threads wird daher das Konzept des Thread-Pools vorgeschlagen, um mehrere Threads zu erstellen. Nach der Ausführung einer Aufgabe bleibt der Thread für einen bestimmten Zeitraum bestehen (der Benutzer kann die Überlebenszeit festlegen). Der Leerlauf-Thread wird später besprochen (Einführung). Wenn eine neue Aufgabe kommt, wird der Leerlauf-Thread direkt wiederverwendet, wodurch der Verlust beim Erstellen und Zerstören von Threads vermieden wird. Natürlich sind Leerlauf-Threads auch eine Verschwendung von Ressourcen (nur die Überlebenszeit von Leerlauf-Threads ist begrenzt), aber es ist viel besser, als häufig Threads zu erstellen und zu zerstören.
Das Folgende ist mein Testcode


  /*
   * @TODO 线程池测试
   */
  @Test
  public void threadPool(){

    /*java提供的统计线程运行数,一开始设置其值为50000,每一个线程任务执行完
     * 调用CountDownLatch#coutDown()方法(其实就是自减1)
     * 当所有的线程都执行完其值就为0
    */
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    Executor pool = Executors.newFixedThreadPool(10);//开启线程池最多会创建10个线程
    for(int i=0;i<50000;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50个线程都执行完了,共用时:"+(end-start)+"ms");
  }


  /**
   *@TODO 手动创建线程测试 
   */
  @Test
  public void thread(){
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
      Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
      thread.start();
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50000个线程都执行完了,共用时:"+(end-start)+"ms");


  }

Die Ausführung mit 5-W-Threads im Thread-Pool dauert etwa 400 ms und die Ausführung ohne Verwendung etwa 4350 ms Die Effizienz des Thread-Pools ist sichtbar ( Leser können es selbst testen, aber aufgrund unterschiedlicher Computerkonfigurationen ist der Datenverbrauch unterschiedlich, aber die Verwendung des Thread-Pools ist definitiv schneller als das Erstellen von Threads).

Wie verwendet Java den Thread-Pool?

Der Thread-Pool wurde im obigen Testcode verwendet und wird im Folgenden offiziell vorgestellt.

Die oberste Ebene aller Java-Thread-Pools ist eine Executor-Schnittstelle, die nur eine Ausführungsmethode hat, die zum Ausführen aller Aufgaben verwendet wird. Java stellt außerdem die ExecutorService-Schnittstelle bereit, die von Executor erbt und die Methoden erweitert. Das Folgende ist AbstractExecutorService, eine abstrakte Klasse, die ExecutorService implementiert. Schließlich erbt ThreadPoolExecutor von der oben genannten abstrakten Klasse. Der Java-Thread-Pool, den wir häufig verwenden, ist eine Instanz dieser Klasse.

Die oben verwendeten Executoren sind eine Toolklasse. Es handelt sich um einen syntaktischen Zucker, der die Thread-Pool-Parameter verschiedener Unternehmen für uns kapselt und neue Operationen ausführt.


 public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue<Runnable>());
  }

Das Obige ist der Quellcode von Executors.newFixedThreadPool(10).

Der nächste Punkt ist hier. Lassen Sie uns über die Bedeutung jedes Parameters der ThreadPoolExecutor-Konstruktionsmethode sprechen.


 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler)

Die obige Konstruktionsmethode ist die umfassendste.

Im Folgenden erklären wir die Bedeutung einiger Parameter anhand des Quellcodes, was überzeugender sein wird.

Das Folgende ist die ThreadPoolExecutor#execute-Methode, bei der es sich um den tatsächlichen Executor von Execute handelt, der von der obigen Schnittstelle aufgerufen wird.


 public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
        return;
      c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
      if (! isRunning(recheck) && remove(command))
        reject(command);
      else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
    }
    else if (!addWorker(command, false))
      reject(command);
  }

ctl ist eine AtomicInteger-Instanz, eine Klasse, die CAS-Operationen für atomare Anweisungen bereitstellt. Sie wird verwendet, um die Anzahl der Threads aufzuzeichnen, die derzeit im Thread-Pool ausgeführt werden -2^29, die workCountOf-Methode erhält ihren absoluten Wert (Sie können sehen, wie sie im Quellcode implementiert wird. Wenn er kleiner als corePoolSize ist, wird die addWorker-Methode aufgerufen (sie wird zum Erstellen eines neuen Workers verwendet). Der Worker erstellt einen Thread, also erstellt er eine Thread-Methode. AddWorkd vergleicht ihn beim Erstellen eines Threads mit dem Wert von corePoolSize oder maxnumPoolSize (wenn true übergeben wird, wird er basierend auf corePoolSize verglichen, false wird verglichen). basierend auf maxnumPoolSize, und wenn der Wert größer oder gleich diesem Wert ist, schlägt die Erstellung fehl. Es ist ersichtlich, dass, wenn die Anzahl der aktuell ausgeführten Threads kleiner als corePoolSize ist, sie erstellt werden und erfolgreich erstellt werden (
erläutert nur kurz den Thread-Pool im Status „Running“).

Wenn die Anzahl der laufenden Threads größer oder gleich corePoolSize ist, wird bei Eingabe des zweiten if isRunning mit SHUTDOWN verglichen (sein Wert = 0). Wie bereits erwähnt, ist c gleich der Anzahl der aktuell laufenden Threads Threads plus -2^ 29. Wenn die aktuell ausgeführten Thread-Daten 2 ^ 29 erreichen, ist ihr Wert = 0, isRunning gibt false zurück, und die Ausführung von addWorkd in else gibt ebenfalls false zurück (addWorkd überprüft es auch), was bedeutet, dass der Thread-Pool Kann 2^29 gleichzeitig laufende Threads unterstützen (ausreichend).

workQueue.offer (Befehl) dient zum Hinzufügen des ausführbaren Objekts zur Warteschlange. Nach dem Beitritt zur Warteschlange ruft die runWorker-Methode die Aufgabenausführung aus der Warteschlange ab. Wenn die aktuelle Warteschlange eine begrenzte Warteschlange (ArrayBlockingQueue) verwendet, gibt das Angebot „false“ zurück. Hier wird „False“ eingegeben, was darauf hinweist, dass der Thread mit „maxnumPoolSize“ verglichen wird Wird hier ausgeführt Die Zahl ist größer oder gleich maxnumPoolSize, dann wird diese Thread-Aufgabe vom Thread-Pool abgelehnt und Reject(Befehl) ausgeführt. Die Ablehnungsmethode verwendet den RejectedExecutionHandler (Ablehnungsstrategie) in unserer ThreadPoolExecutor-Konstruktionsmethode, die erläutert wird im Detail später.

Nach der obigen Einführung in Kombination mit dem Quellcode ist die folgende Einführung in die Parameter unseres ThreadPoolExecutors leicht zu verstehen.

Thread-Erstellungs- und Ablehnungsstrategien im Thread-Pool

corePoolSize, maxnumPoolSize und BlockingQueue sollten gemeinsam besprochen werden

Wenn die Anzahl der im Thread-Pool ausgeführten Threads kleiner als corePoolSize ist, erstellt eine neue Thread-Aufgabe immer einen neuen Thread zur Ausführung. Wenn sie größer als corePoolSize ist, wird die Aufgabe zur Warteschlange blockingQueue hinzugefügt Sie übergeben eine unbegrenzte Warteschlange (LinkedBlockingQueue). Dies ist eine Warteschlange, die „unendlich viele“ Aufgaben speichern kann. Es hat nichts mit maxnumPoolSize zu tun Die Anzahl der Threads im Thread-Pool beträgt corePoolSize; wenn Sie jedoch eine begrenzte Warteschlange (ArrayBlockingQueue, SynchronousQueue) übergeben, werden neue Threads erstellt, wenn die Warteschlange voll ist und die Anzahl der Threads kleiner als maxmunPoolSize ist, bis die Anzahl der Threads größer als maxnumPoolSize ist ; wenn die Anzahl der Threads größer als maxnumPoolSize ist, wird die Beitrittsaufgabe vom Thread-Pool abgelehnt.

RejectedExecutionHandler-Ablehnungsstrategie Java implementiert 4 AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy Benutzer können diese Schnittstelle auch implementieren, um ihre eigene Ablehnungsstrategie zu implementieren. Die zweite Möglichkeit besteht darin, eine Ausnahme auszulösen die neue Aufgabe direkt auszuführen; die dritte besteht darin, die älteste Aufgabe in der Warteschlange abzubrechen; die vierte besteht darin, die aktuelle Aufgabe abzubrechen.

Das obige ist der detaillierte Inhalt vonEine ausführliche Erläuterung der Verwendung und Prinzipien von Thread-Pools in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn