Java-Beispiel für die Verwendung eines Thread-Pools zum Ausführen mehrerer Aufgaben

In diesem Artikel werden hauptsächlich Beispiele für Java vorgestellt, bei denen der Thread-Pool zum Ausführen mehrerer Aufgaben verwendet wird, damit jeder Java besser verstehen und lernen kann. Interessierte Freunde können mehr darüber erfahren

Wenn Sie eine Reihe von nicht zusammenhängenden asynchronen Aufgaben mit E / A-Vorgängen ausführen (z. B. das Herunterladen von Dateien), kann die Verwendung von Multithreading die Betriebseffizienz erheblich verbessern. Der Thread-Pool enthält eine Reihe von Threads und kann diese Threads verwalten. Zum Beispiel: Thread erstellen, Thread zerstören usw. In diesem Artikel wird erläutert, wie Sie den Thread-Pool in Java zum Ausführen von Aufgaben verwenden.

1 Aufgabentyp

Bevor Sie den Thread-Pool zum Ausführen von Aufgaben verwenden, müssen Sie herausfinden, welche Aufgaben vom Thread-Pool aufgerufen werden können. Je nachdem, ob die Aufgabe einen Rückgabewert hat, können Aufgaben in zwei Typen unterteilt werden, nämlich die Aufgabenklasse, die Runnable implementiert (keine Parameter und kein Rückgabewert), und die Aufgabenklasse, die die Callable-Schnittstelle implementiert (keine Parameter und Rückgabewert). . Wählen Sie beim Codieren den entsprechenden Aufgabentyp entsprechend Ihren Anforderungen aus.

1.1 Eine Klasse, die die Runnable-Schnittstelle implementiert

Bei Multithread-Aufgabentypen fällt natürlich als erstes die Klasse ein, die die Runnable-Schnittstelle implementiert. Die Runnable-Schnittstelle bietet einen abstrakten Methodenlauf, der keine Parameter und keinen Rückgabewert enthält. Z.B:

Runnable task = new Runnable() {
    
    
  @Override
  public void run() {
    
    
    System.out.println("Execute task.");
  }
};

Oder eine einfachere Schreibweise in Java 8 und höher

Runnable task = ()->{
    
    
  System.out.println("Execute task.");
};

1.2 Klassen, die die Callable-Schnittstelle implementieren Wie
Runnable verfügt Callable nur über eine abstrakte Methode, die abstrakte Methode hat jedoch einen Rückgabewert. Die Art des Rückgabewerts muss bei der Implementierung dieser Schnittstelle formuliert werden. Z.B:

Callable<String> callableTask = ()-> "finished";

Wie Runnable hat Callable nur eine abstrakte Methode, aber diese abstrakte Methode hat einen Rückgabewert. Die Art des Rückgabewerts muss bei der Implementierung dieser Schnittstelle formuliert werden. Z.B:

Callable<String> callableTask = ()-> "finished";

2 Thread-Pool-Typ

java.util.concurrent.Executors bietet eine Reihe statischer Methoden zum Erstellen verschiedener Thread-Pools. Einige der Haupt-Thread-Pools und -Eigenschaften sind unten aufgeführt, und die Eigenschaften anderer Thread-Pools, die nicht aufgeführt sind, können aus den folgenden abgeleitet werden.

2.1 Fester Thread-Pool mit einer festen Anzahl von Threads

Wie der Name schon sagt, ist die Anzahl der Threads in dieser Art von Thread-Pool festgelegt. Wenn die Anzahl der Threads auf n festgelegt ist, werden zu jedem Zeitpunkt höchstens n Threads im Thread-Pool ausgeführt. Wenn sich der Thread-Pool in einem gesättigten Betriebszustand befindet, werden die an den Thread-Pool gesendeten Aufgaben in die Ausführungswarteschlange gestellt. Wenn der Thread-Pool nicht gesättigt ist, ist der Thread-Pool immer vorhanden, bis die Shutdown-Methode von ExecuteService aufgerufen wird. Der Thread-Pool wird gelöscht.

// 创建线程数量为5的线程池。
ExecutorService executorService = Executors.newFixedThreadPool(5);

2.2 Zwischengespeicherter Thread-Pool
Diese Art von Thread-Pool hat eine Anfangsgröße von 0 Threads. Wenn Aufgaben kontinuierlich an den Pool gesendet werden, wenn der Thread-Pool keine Leerlauf-Threads enthält (0 Threads bedeuten auch, dass keine Leerlauf-Threads vorhanden sind) Ein neuer Thread wird erstellt, um sicherzustellen, dass keine Aufgabe wartet. Wenn ein Leerlauf-Thread vorhanden ist, wird der Leerlauf-Status-Thread zur Ausführung der Aufgabe wiederverwendet. Threads, die sich im Leerlauf befinden, werden nur 60 Sekunden lang im Thread-Pool zwischengespeichert, und Threads, die sich 60 Sekunden lang im Leerlauf befanden, werden heruntergefahren und aus dem Thread-Pool entfernt. Es kann die Programmleistung erheblich verbessern, wenn eine große Anzahl von kurzlebigen (offiziell: kurzlebigen) asynchronen Aufgaben ausgeführt wird.

//创建一个可缓存的线程池 
ExecutorService executorService = Executors.newCachedThreadPool();

2.3 Single-Threaded-Pool
Dies kann nicht als Thread-Pool bezeichnet werden, da immer nur ein Thread darin ist und es nur einen von Anfang bis Ende gibt (warum es heißt, dass es sich von Executors.newFixedThreadPool (1) unterscheidet). Nennen Sie es also immer noch "Single-Threaded-Pool-Handle". Sie können dem Single-Threaded-Pool so viele Aufgaben wie möglich hinzufügen, aber jedes Mal wird nur eine ausgeführt, und die Aufgaben werden nacheinander ausgeführt. Wenn in der vorherigen Aufgabe eine Ausnahme auftritt, wird der aktuelle Thread zerstört, es wird jedoch ein neuer Thread erstellt, um die folgenden Aufgaben auszuführen. Die obigen Angaben entsprechen denen des Pools mit festen Threads mit nur einem Thread. Der einzige Unterschied zwischen beiden besteht darin, dass Executors.newFixedThreadPool (1) die Anzahl der darin enthaltenen Threads zur Laufzeit ändern kann, während Executors.newSingleThreadExecutor () immer nur 1 Thread haben kann.

//创建一个单线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();

2.4 Thread-Pool zum Stehlen von Arbeit
Öffnen Sie den Quellcode, und Sie werden feststellen, dass der Thread-Pool zum Stehlen von Arbeit im Wesentlichen ForkJoinPool ist. Diese Art von Thread-Pool nutzt CPU-Mehrkern-Verarbeitungsaufgaben vollständig aus und eignet sich für Verarbeitungsaufgaben, die mehr CPU-Ressourcen verbrauchen. Die Anzahl der Threads ist nicht festgelegt, und es werden mehrere Taskwarteschlangen verwaltet. Wenn eine Taskwarteschlange abgeschlossen ist, stiehlt der entsprechende Thread die Taskausführung aus anderen Taskwarteschlangen. Dies bedeutet auch, dass die Taskausführungsreihenfolge mit der Übermittlung übereinstimmt bestellen. Bei höherer Nachfrage können Sie den Thread-Pool direkt über ForkJoinPool abrufen.

//创建一个工作窃取线程池,使用CPU核数等于机器的CPU核数
ExecutorService executorService = Executors.newWorkStealingPool();

//创建一个工作窃取线程池,使用CPU 3 个核进行计算,工作窃取线程池不能设置线程数
ExecutorService executorService2 = Executors.newWorkStealingPool(3);

2.5 Geplanter Task-Thread-Pool Der
geplante Task-Thread-Pool kann bestimmte Aufgaben gemäß dem Plan ausführen, z. B.: Eine bestimmte Task regelmäßig ausführen.

// 获取一个大小为2的计划任务线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 添加一个打印当前线程信息计划任务,该任务在3秒后执行
scheduledExecutorService.schedule(() -> {
    
     System.out.println(Thread.currentThread()); }, 3, TimeUnit.SECONDS);
// 添加一个打印当前线程信息计划任务,该任务在2秒后首次执行,之后每5秒执行一次。如果任务执行时间超过了5秒,则下一次将会在前一次执行完成之后立即执行
scheduledExecutorService.scheduleAtFixedRate(() -> {
    
     System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);
// 添加一个打印当前线程信息计划任务,该任务在2秒后首次执行,之后每次在任务执行之后5秒执行下一次。
scheduledExecutorService.scheduleWithFixedDelay(() -> {
    
     System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);
// 逐个清除 idle 状态的线程
scheduledExecutorService.shutdown();
// 阻塞,在线程池被关调之前代码不再往下走
scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

3 Verwenden Sie den Thread-Pool, um Aufgaben auszuführen

Wie bereits erwähnt, werden die Aufgabentypen in Typen mit Rückgabewert und ohne Rückgabewert unterteilt, und Aufrufe werden hier auch in Aufrufe mit Rückgabewert und Aufrufe ohne Rückgabewert unterteilt.

3.1 Aufrufen einer Aufgabe ohne Rückgabewert

Wenn es sich um einen Aufruf einer Aufgabe ohne Rückgabewert handelt, können Sie die Methode execute oder submit verwenden. In diesem Fall sind die beiden im Wesentlichen identisch. Um die Einheitlichkeit beim Aufrufen von Aufgaben mit Rückgabewerten zu gewährleisten, wird empfohlen, die Methode submit zu verwenden.

//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);

//提交一个无返回值的任务(实现了Runnable接口)
executorService.submit(()->System.out.println("Hello"));

executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

Wenn es eine Reihe von Aufgaben gibt, können diese einzeln eingereicht werden.

//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Runnable> tasks = Arrays.asList(
    ()->System.out.println("Hello"),
    ()->System.out.println("World"));

//逐个提交任务
tasks.forEach(executorService::submit);

executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

3.2 Aufrufe von Aufgaben mit Rückgabewerten Aufgaben
mit Rückgabewerten müssen die Callable-Schnittstelle implementieren. Geben Sie bei der Implementierung den Rückgabewerttyp an der generischen Position an. Wenn die Submit-Methode aufgerufen wird, wird ein Future-Objekt zurückgegeben, und der Rückgabewert kann über die Future-Methode get () abgerufen werden. Hierbei ist zu beachten, dass der Code beim Aufruf von get () blockiert wird, bis die Aufgabe abgeschlossen ist und ein Rückgabewert vorliegt.

ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<String> future = executorService.submit(()->"Hello");
System.out.println(future.isDone());//false
String value = future.get();
System.out.println(future.isDone());//true
System.out.println(value);//Hello

Wenn Sie einen Stapel von Aufgaben senden möchten, kann ExecutorService nicht nur eine nach der anderen senden, sondern auch invokeAll zur einmaligen Übermittlung aufrufen. Tatsächlich besteht die interne Implementierung von invokeAll darin, Aufgaben einzeln in einer Schleife zu übermitteln. Der von invokeAll zurückgegebene Wert ist eine zukünftige Liste.

ExecutorService executorService = Executors.newFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(()->"Hello", ()->"World");
List<Future<String>> futures = executorService.invokeAll(tasks);

Die invokeAny-Methode ist ebenfalls sehr nützlich. Der Thread-Pool führt mehrere Aufgaben aus, die Callable implementieren, und gibt dann den Wert der ersten Aufgabe zurück, deren Ausführung abgeschlossen wurde. Andere nicht abgeschlossene Aufgaben werden ausnahmslos normal abgebrochen. Der folgende Code gibt nicht "Hallo" aus.

ExecutorService executorService = Executors.newFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(
    () -> {
    
    
      Thread.sleep(500L);
      System.out.println("Hello");
      return "Hello";
    }, () -> {
    
    
      System.out.println("World");
      return "World";
    });
String s = executorService.invokeAny(tasks);
System.out.println(s);//World

Ausgabe:

World
World

Beim Betrachten des ExecutorService-Quellcodes stellte ich außerdem fest, dass er auch eine Methode Future submit (ausführbare Aufgabe, T-Ergebnis) enthält. Sie können eine Aufgabe senden, die die ausführbare Schnittstelle über diese Methode implementiert, und dann den Wert zurückgeben. und die run-Methode in der Runnable-Schnittstelle Wenn kein Rückgabewert vorhanden ist. Woher kommt der Rückgabewert? Tatsächlich liegt das Problem in einem Parameter nach der Submit-Methode, und der Parameterwert ist der zurückgegebene Wert. Nach dem Aufrufen der Submit-Methode gibt es eine Operation, und der Ergebnisparameter wird direkt zurückgegeben.

ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(() -> System.out.println("Hello"), "World");
System.out.println(future.get());//输出:World

Wenn Sie Multithreading zum Verarbeiten von Aufgaben verwenden, sollten Sie je nach Situation den entsprechenden Aufgabentyp und Thread-Pooltyp auswählen. Wenn es keinen Rückgabewert gibt, können Sie die Aufgabe verwenden, die die Runnable- oder Callable-Schnittstelle implementiert. Wenn es einen Rückgabewert gibt, sollten Sie die Aufgabe verwenden, die die Callable-Schnittstelle implementiert, und der Rückgabewert wird über die get-Methode von Future abgerufen . Wenn bei der Auswahl eines Thread-Pools nur 1 Thread verwendet wird, verwenden Sie einen einzelnen Thread-Pool oder einen Thread-Pool mit fester Kapazität und einer Kapazität von 1, um eine große Anzahl von kurzlebigen Aufgaben zu verarbeiten, verwenden Sie einen zwischenspeicherbaren Thread-Pool Planen oder Ausführen einiger Schleifen Aufgaben können den geplanten Task-Thread-Pool verwenden. Wenn die Task viele CPU-Ressourcen verbrauchen muss, stiehlt die Anwendungsarbeit den Thread-Pool.

Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass er für alle hilfreich ist, und ich hoffe, dass Sie ihn unterstützen können

Ich denke du magst

Origin blog.csdn.net/p1830095583/article/details/114701223
Empfohlen
Rangfolge