Java thread pool beheert de pool van worker threads. Het bevat een wachtrij waarin taken wachten om uitgevoerd te worden. We kunnen ThreadPoolExecutor
gebruiken om een thread pool in Java te maken. Java thread pool beheert de verzameling van Runnable threads. De worker threads voeren Runnable threads uit de wachtrij uit. java.util.concurrent.Executors bieden fabrieks- en ondersteuningsmethoden voor java.util.concurrent.Executor interface om de thread pool in Java te maken. Executors is een hulpprogrammaklasse die ook nuttige methoden biedt om te werken met ExecutorService, ScheduledExecutorService, ThreadFactory, en Callable klassen via verschillende fabrieksmethoden. Laten we een eenvoudig programma schrijven om de werking ervan uit te leggen. Eerst hebben we een Runnable klasse nodig, genaamd WorkerThread.java
package com.journaldev.threadpool;
public class WorkerThread implements Runnable {
private String command;
public WorkerThread(String s){
this.command=s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" Start. Command = "+command);
processCommand();
System.out.println(Thread.currentThread().getName()+" End.");
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString(){
return this.command;
}
}
ExecutorService Voorbeeld
Hier is de testprogrammaklasse SimpleThreadPool.java
, waar we een vaste thread pool maken vanuit Executors-framework.
package com.journaldev.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
In het bovenstaande programma maken we een thread pool van vaste grootte van 5 worker threads. Vervolgens dienen we 10 taken in bij deze pool, aangezien de poolgrootte 5 is, zal het werken aan 5 taken en andere taken zullen in de wachtstaat zijn, zodra een van de taken is voltooid, zal een andere taak uit de wachtrij door de worker thread worden opgepakt en worden uitgevoerd. Hier is de uitvoer van het bovenstaande programma.
pool-1-thread-2 Start. Command = 1
pool-1-thread-4 Start. Command = 3
pool-1-thread-1 Start. Command = 0
pool-1-thread-3 Start. Command = 2
pool-1-thread-5 Start. Command = 4
pool-1-thread-4 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
pool-1-thread-3 End.
pool-1-thread-3 Start. Command = 8
pool-1-thread-2 End.
pool-1-thread-2 Start. Command = 9
pool-1-thread-1 Start. Command = 7
pool-1-thread-5 Start. Command = 6
pool-1-thread-4 Start. Command = 5
pool-1-thread-2 End.
pool-1-thread-4 End.
pool-1-thread-3 End.
pool-1-thread-5 End.
pool-1-thread-1 End.
Finished all threads
De uitvoer bevestigt dat er vijf threads in de pool zijn met de namen van “pool-1-thread-1” tot “pool-1-thread-5” en dat ze verantwoordelijk zijn voor het uitvoeren van de ingediende taken naar de pool.
Voorbeeld van ThreadPoolExecutor
De Executors-klasse biedt een eenvoudige implementatie van de ExecutorService met behulp van ThreadPoolExecutor, maar ThreadPoolExecutor biedt veel meer functies dan dat. We kunnen het aantal threads specificeren dat actief zal zijn wanneer we een instantie van ThreadPoolExecutor maken, en we kunnen de grootte van de thread-pool beperken en onze eigen implementatie van RejectedExecutionHandler maken om de taken te verwerken die niet in de wachtrij van de werker passen. Hier is onze aangepaste implementatie van de RejectedExecutionHandler-interface.
package com.journaldev.threadpool;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString() + " is rejected");
}
}
ThreadPoolExecutor
biedt verschillende methoden waarmee we de huidige status van de uitvoerder, de grootte van de pool, het aantal actieve threads en het taaktelling kunnen achterhalen. Daarom heb ik een monitorthread die de uitvoerderinformatie op bepaalde tijdsintervallen afdrukt.
package com.journaldev.threadpool;
import java.util.concurrent.ThreadPoolExecutor;
public class MyMonitorThread implements Runnable
{
private ThreadPoolExecutor executor;
private int seconds;
private boolean run=true;
public MyMonitorThread(ThreadPoolExecutor executor, int delay)
{
this.executor = executor;
this.seconds=delay;
}
public void shutdown(){
this.run=false;
}
@Override
public void run()
{
while(run){
System.out.println(
String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s",
this.executor.getPoolSize(),
this.executor.getCorePoolSize(),
this.executor.getActiveCount(),
this.executor.getCompletedTaskCount(),
this.executor.getTaskCount(),
this.executor.isShutdown(),
this.executor.isTerminated()));
try {
Thread.sleep(seconds*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Hier is een voorbeeld van de thread-poolimplementatie met ThreadPoolExecutor.
package com.journaldev.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class WorkerPool {
public static void main(String args[]) throws InterruptedException{
//Implementatie van RejectedExecutionHandler
RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
//Haal de ThreadFactory-implementatie op om te gebruiken
ThreadFactory threadFactory = Executors.defaultThreadFactory();
//aanmaken van de ThreadPoolExecutor
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(2), threadFactory, rejectionHandler);
//start de monitor thread
MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
Thread monitorThread = new Thread(monitor);
monitorThread.start();
//werk indienen aan de threadpool
for(int i=0; i<10; i++){
executorPool.execute(new WorkerThread("cmd"+i));
}
Thread.sleep(30000);
//sluit de pool af
executorPool.shutdown();
//sluit de monitor thread af
Thread.sleep(5000);
monitor.shutdown();
}
}
Merk op dat bij het initialiseren van de ThreadPoolExecutor, we de initiële poolsitegrootte houden op 2, de maximale poolsitegrootte op 4 en de grootte van de werkqueue op 2. Dus als er 4 lopende taken zijn en er meer taken worden ingediend, zal de werkqueue er slechts 2 vasthouden en de rest ervan zal worden afgehandeld door RejectedExecutionHandlerImpl
. Hier is de output van het bovenstaande programma dat de bovenstaande verklaring bevestigt.
pool-1-thread-1 Start. Command = cmd0
pool-1-thread-4 Start. Command = cmd5
cmd6 is rejected
pool-1-thread-3 Start. Command = cmd4
pool-1-thread-2 Start. Command = cmd1
cmd7 is rejected
cmd8 is rejected
cmd9 is rejected
[monitor] [0/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 4, Completed: 0, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-4 End.
pool-1-thread-1 End.
pool-1-thread-2 End.
pool-1-thread-3 End.
pool-1-thread-1 Start. Command = cmd3
pool-1-thread-4 Start. Command = cmd2
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
[monitor] [4/2] Active: 2, Completed: 4, Task: 6, isShutdown: false, isTerminated: false
pool-1-thread-1 End.
pool-1-thread-4 End.
[monitor] [4/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [2/2] Active: 0, Completed: 6, Task: 6, isShutdown: false, isTerminated: false
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true
[monitor] [0/2] Active: 0, Completed: 6, Task: 6, isShutdown: true, isTerminated: true
Merk op de verandering in actieve, voltooide en totaal voltooide taaktelling van de executor. We kunnen de methode shutdown() oproepen om de uitvoering van alle ingediende taken te voltooien en de threadpool te beëindigen. Als u een taak wilt plannen om met vertraging of periodiek uit te voeren, dan kunt u de klasse ScheduledThreadPoolExecutor gebruiken. Lees meer hierover op Java Schedule Thread Pool Executor.