1. Einfache und grobe Threads
Wenn wir eine Aufgabe parallel oder asynchron ausführen möchten, verwenden wir direkt die Methode zum Starten eines Threads, wie unten gezeigt:
new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub 这里放你要执行的方法 } }).start();
Allerdings gibt es viele Nachteile bei der Verwendung jedes Mals eines neuen Threads wie oben oder ähnlicher Methoden, wie etwa den folgenden:
Die Leistung beim Erstellen neuer Objekte jedes Mal, wenn ein neuer Thread erstellt wird, ist schlecht ;
Threads können unbegrenzt viele neue Threads erstellen und miteinander konkurrieren. Sie können auch zu viele Systemressourcen beanspruchen und zu Abstürzen oder OOM (Out of Memory) führen.
Mangel an mehr Funktionen wie geplante Ausführung, periodische Ausführung, Thread-Unterbrechung usw.
2. Thread-Pool
Um diese Probleme zu lösen, wurde nach Jdk1.5 das Paket java.util.concurrent hinzugefügt. Dieses Paket führt hauptsächlich die Verwendung von Threads und Thread-Pools in Java ein . Es bietet uns eine große Hilfe bei der Bewältigung von Thread-Problemen in der Entwicklung.
1. Funktion
Je nach Systemumgebung kann die Anzahl der Threads automatisch oder manuell eingestellt werden, um den besten Betriebseffekt zu erzielen. Weniger verschwendet Systemressourcen und mehr führt zu einer Überlastung des Systems und Ineffizienz hoch. Verwenden Sie den Thread-Pool, um die Anzahl der Threads zu steuern, und andere Threads werden in die Warteschlange gestellt, um zu warten. Nachdem eine Aufgabe ausgeführt wurde, wird die vorderste Aufgabe in der Warteschlange übernommen und mit der Ausführung begonnen. Wenn sich in der Warteschlange kein Warteprozess befindet, wartet diese Ressource des Thread-Pools. Wenn eine neue Aufgabe ausgeführt werden muss und sich im Thread-Pool wartende Arbeitsthreads befinden, kann sie ausgeführt werden. Andernfalls gelangt sie in die Warteschlange.
2. Warum einen Thread-Pool verwenden?
Vorhandene Threads wiederverwenden, um die Kosten für die Objekterstellung und -zerstörung zu reduzieren und eine gute Leistung zu erzielen.
Es kann die maximale Anzahl gleichzeitiger Threads effektiv steuern, die Nutzung von Systemressourcen verbessern und übermäßigen Ressourcenwettbewerb und Überlastung vermeiden.
Bietet Funktionen wie geplante Ausführung, periodische Ausführung, Einzelthread und Parallelitätskontrolle.
Sie können die Anzahl der Arbeitsthreads im Thread-Pool entsprechend der Systemkapazität anpassen, um zu verhindern, dass der Server aufgrund übermäßigen Speicherverbrauchs erschöpft wird (jeder Thread benötigt etwa 1 MB Speicher, je länger der Thread geöffnet ist). , Wenn es zu groß ist, verbraucht es mehr Speicher und stürzt schließlich ab.
3. Hauptklassen
Die oberste Ebene des Thread-Pools in Java ist Executor, aber streng genommen ist Executor kein Thread-Pool, sondern nur ein Tool zum Ausführen von Threads. Die eigentliche Thread-Pool-Schnittstelle ist ExecutorService.
Das Konfigurieren eines Thread-Pools ist relativ kompliziert, insbesondere wenn das Prinzip des Thread-Pools nicht ganz klar ist nicht optimal sein, daher werden in der Executors-Klasse einige statische Fabriken bereitgestellt, um einige häufig verwendete Thread-Pools zu generieren.
1) newSingleThreadExecutor
Erstellen Sie einen Single-Threaded-Thread-Pool. In diesem Thread-Pool arbeitet nur ein Thread, was einem einzelnen Thread entspricht, der alle Aufgaben seriell ausführt. Wenn der einzige Thread abnormal endet, wird er durch einen neuen Thread ersetzt. Dieser Thread-Pool stellt sicher, dass alle Aufgaben in der Reihenfolge ausgeführt werden, in der sie übermittelt werden.
2) newFixedThreadPool
Erstellen Sie einen Thread-Pool mit fester Größe. Bei jeder Übermittlung einer Aufgabe wird ein Thread erstellt, bis der Thread die maximale Größe des Thread-Pools erreicht. Die Größe des Thread-Pools bleibt unverändert, sobald der Maximalwert erreicht ist. Wenn ein Thread aufgrund einer Ausführungsausnahme endet, wird der Thread-Pool mit einem neuen Thread aufgefüllt.
3) newCachedThreadPool
Erstellen Sie einen zwischenspeicherbaren Thread-Pool. Wenn die Größe des Thread-Pools die zur Verarbeitung der Aufgabe erforderlichen Threads überschreitet,
Dann werden einige inaktive Threads (die 60 Sekunden lang keine Aufgaben ausführen) recycelt. Wenn die Anzahl der Aufgaben zunimmt, kann dieser Thread-Pool recycelt werden Intelligent hinzugefügt Neuer Thread zur Bearbeitung der Aufgabe. Dieser Thread-Pool begrenzt die Größe des Thread-Pools nicht. Die Größe des Thread-Pools hängt vollständig von der maximalen Thread-Größe ab, die das Betriebssystem (oder die JVM) erstellen kann.
4) newScheduledThreadPool
Erstellen Sie einen Thread-Pool mit unbegrenzter Größe. Dieser Thread-Pool unterstützt das Timing und die periodische Ausführung von Aufgaben.
3. Beispiele
1) newCachedThreadPool
Wenn die Länge des Thread-Pools den Verarbeitungsbedarf übersteigt, können inaktive Threads flexibel recycelt werden , erstelle einen neuen Thread. Der Beispielcode lautet wie folgt:
package test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExecutorTest { public static void main(String[] args) { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; try { Thread.sleep(index * 1000); } catch (InterruptedException e) { e.printStackTrace(); } cachedThreadPool.execute(new Runnable() { public void run() { System.out.println(index); } }); } } }
Der Thread-Pool ist unendlich. Wenn die zweite Aufgabe ausgeführt wird, ist die erste Aufgabe abgeschlossen und der Thread, der die erste Aufgabe ausführt, wird wiederverwendet, anstatt einen zu erstellen Jedes Mal ein neuer Thread.
2) newFixedThreadPool
Erstellen Sie einen Thread-Pool mit fester Länge, der die maximale Anzahl gleichzeitiger Threads steuern kann, die in der Warteschlange warten. Der Beispielcode lautet wie folgt:
package test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExecutorTest { public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } }
Da die Thread-Pool-Größe 3 beträgt, schläft jede Aufgabe nach der Ausgabe des Index 2 Sekunden lang, sodass alle zwei Sekunden 3 Zahlen gedruckt werden.
Die Größe des Thread-Pools fester Länge wird am besten entsprechend den Systemressourcen festgelegt. Wie zum Beispiel Runtime.getRuntime().availableProcessors()
3)newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
package test; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ThreadPoolExecutorTest { public static void main(String[] args) { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { public void run() { System.out.println("delay 3 seconds"); } }, 3, TimeUnit.SECONDS); } }
表示延迟3秒执行。
定期执行示例代码如下:
package test; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ThreadPoolExecutorTest { public static void main(String[] args) { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.scheduleAtFixedRate(new Runnable() { public void run() { System.out.println("delay 1 seconds, and excute every 3 seconds"); } }, 1, 3, TimeUnit.SECONDS); } }
表示延迟1秒后每3秒执行一次。
4)newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:
package test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExecutorTest { public static void main(String[] args) { ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { public void run() { try { System.out.println(index); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } }
结果依次输出,相当于顺序执行各个任务。
注意:以上的execute()方法可以替换为submit()方法,执行的结果是一样的。
四、submit()和execute()的区别
JDK5往后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,它们的区别是:
execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口
submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口