Verstehen Sie den Artikel und verstehen Sie die ausführliche Erklärung des Java-Thread-Pools – ThreadPoolExecutor

Zuerst mit Fragen zum Verständnis

  • Warum gibt es einen Thread-Pool?
  • Auf welche Weise implementiert und verwaltet Java den Thread-Pool? Bitte geben Sie ein einfaches Beispiel für die Verwendung.
  • Warum ist es vielen Unternehmen nicht gestattet, Executors zum Erstellen von Thread-Pools zu verwenden? Wie empfehlen Sie deren Verwendung?
  • Was sind die Kernkonfigurationsparameter von ThreadPoolExecutor? Bitte erläutern Sie sie kurz
  • Welche drei Thread-Pools kann ThreadPoolExecutor erstellen?
  • Was passiert, wenn die Warteschlange voll ist und die Anzahl der Arbeiter maxSize erreicht?
  • Sagen Sie mir, welche RejectedExecutionHandler-Strategien ThreadPoolExecutor hat? Was ist die Standardstrategie?
  • Sprechen Sie kurz über den Task-Ausführungsmechanismus des Thread-Pools?execute –> addWorker –>runworker (getTask)
  • Wie werden Aufgaben im Thread-Pool eingereicht?
  • Wie werden Aufgaben im Thread-Pool geschlossen?
  • Welche Konfigurationsfaktoren müssen bei der Konfiguration des Thread-Pools berücksichtigt werden?
  • Wie überwache ich den Status des Thread-Pools?

 Warum einen Thread-Pool haben?

Der Thread-Pool kann Threads einheitlich zuweisen, optimieren und überwachen:

  • Reduzieren Sie den Ressourcenverbrauch (Threads werden auf unbestimmte Zeit erstellt und nach der Verwendung zerstört)
  • Reaktionsgeschwindigkeit verbessern (keine Notwendigkeit, Threads zu erstellen)
  • Verbessern Sie die Thread-Verwaltbarkeit

# ThreadPoolExecutor-Beispiel

Wie implementiert und verwaltet Java den Thread-Pool?

Ab JDK 5 ist die Arbeitseinheit vom Ausführungsmechanismus getrennt. Die Arbeitseinheit umfasst Runnable und Callable, und der Ausführungsmechanismus wird vom Executor-Framework bereitgestellt.

  • WorkerThread
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;
    }
}
  • SimpleThreadPool
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(); // This will make the executor accept no new threads and finish all existing threads in the queue
        while (!executor.isTerminated()) { // Wait until all threads are finish,and also you can use "executor.awaitTermination();" to wait
        }
        System.out.println("Finished all threads");
    }

}

Im Programm haben wir einen Thread-Pool mit einer festen Größe von fünf Worker-Threads erstellt. Weisen Sie dann dem Thread-Pool zehn Jobs zu. Da die Thread-Poolgröße fünf beträgt, werden zunächst fünf Worker-Threads gestartet, um fünf Jobs zu verarbeiten, und die anderen Jobs befinden sich im Wartezustand. Sobald ein Job abgeschlossen ist, wird der Worker-Thread ausgeführt Nehmen Sie es auf, wenn es inaktiv ist. Warten Sie auf die Ausführung anderer Jobs in der Warteschlange.

Hier ist die Ausgabe des obigen Programms.

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

Die Ausgabe zeigt, dass es im Thread-Pool vom Anfang bis zum Ende nur fünf Threads mit den Namen „pool-1-thread-1“ bis „pool-1-thread-5“ gibt und diese fünf Threads nicht mit der Fertigstellung sterben ArbeitEs wird immer existieren und ist für die Ausführung der dem Thread-Pool zugewiesenen Aufgaben verantwortlich, bis der Thread-Pool stirbt.

Die Executors-Klasse stellt eine einfache ExecutorService-Implementierung bereit, die ThreadPoolExecutor verwendet, aber ThreadPoolExecutor bietet noch viel mehr. Wir können beim Erstellen einer ThreadPoolExecutor-Instanz die Anzahl der aktiven Threads angeben, wir können auch die Größe des Thread-Pools begrenzen und unsere eigene Implementierung von RejectedExecutionHandler erstellen, um Arbeiten zu verarbeiten, die nicht in die Arbeitswarteschlange passen.

Hier ist die Implementierung unserer benutzerdefinierten RejectedExecutionHandler-Schnittstelle.

  • RejectedExecutionHandlerImpl.java
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 stellt Methoden bereit, mit denen wir den aktuellen Status des Executors, die Thread-Pool-Größe, die Anzahl der aktiven Threads und die Anzahl der Aufgaben abfragen können. Deshalb verwende ich einen Überwachungsthread, um Executor-Informationen in bestimmten Zeitintervallen zu drucken.

  • MyMonitorThread.java
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 ist eine Beispiel-Thread-Pool-Implementierung mit ThreadPoolExecutor.

  • WorkerPool.java
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{
        //RejectedExecutionHandler implementation
        RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
        //Get the ThreadFactory implementation to use
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //creating the ThreadPoolExecutor
        ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2), threadFactory, rejectionHandler);
        //start the monitoring thread
        MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
        Thread monitorThread = new Thread(monitor);
        monitorThread.start();
        //submit work to the thread pool
        for(int i=0; i<10; i++){
            executorPool.execute(new WorkerThread("cmd"+i));
        }
         
        Thread.sleep(30000);
        //shut down the pool
        executorPool.shutdown();
        //shut down the monitor thread
        Thread.sleep(5000);
        monitor.shutdown();
         
    }
}

Beachten Sie, dass wir beim Initialisieren des ThreadPoolExecutors die anfängliche Poolgröße bei 2, die maximale Poolgröße bei 4 und die Arbeitswarteschlangengröße bei 2 belassen. Wenn also bereits vier ausgeführte Aufgaben vorhanden sind und zu diesem Zeitpunkt weitere Aufgaben zugewiesen werden, enthält die Arbeitswarteschlange nur zwei davon (neue Aufgaben) und die anderen werden vom RejectedExecutionHandlerImpl verarbeitet.

Die Ausgabe des obigen Programms kann den obigen Punkt bestätigen.

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

Beachten Sie die Änderung in der Anzahl der aktiven Aufgaben des Ausführenden, der abgeschlossenen Aufgaben und aller abgeschlossenen Aufgaben. Wir können die Methode „shutdown()“ aufrufen, um alle übermittelten Aufgaben zu beenden und den Thread-Pool zu beenden.

 Detaillierte Verwendung von ThreadPoolExecutor

Tatsächlich ist das Implementierungsprinzip des Java-Thread-Pools sehr einfach: Um es ganz klar auszudrücken: Es handelt sich um ein Thread-Sammlungs-WorkerSet und eine Blockierungswarteschlange-WorkQueue. Wenn der Benutzer eine Aufgabe (dh einen Thread) an den Thread-Pool sendet, stellt der Thread-Pool die Aufgabe zunächst in die WorkQueue. Die Threads in workerSet rufen kontinuierlich Threads von workQueue ab und führen sie aus. Wenn in der WorkQueue keine Aufgabe vorhanden ist, blockiert der Worker, bis sich eine Aufgabe in der Warteschlange befindet, entfernt sie dann und setzt die Ausführung fort.

 Prinzip ausführen

Nachdem eine Aufgabe an den Thread-Pool übermittelt wurde:

  1. Erstens, ob die Anzahl der Threads, die derzeit im Thread-Pool ausgeführt werden, kleiner als corePoolSize ist. Wenn ja, erstellen Sie einen neuen Arbeitsthread, um die Aufgabe auszuführen. Wenn alle Aufgaben ausgeführt werden, fahren Sie mit Schritt 2 fort.
  2. Stellen Sie fest, ob die BlockingQueue voll ist. Wenn nicht, fügen Sie den Thread in die BlockingQueue ein. Andernfalls gehen Sie zu 3.
  3. Wenn das Erstellen eines neuen Arbeitsthreads dazu führen würde, dass die Anzahl der aktuell ausgeführten Threads die maximale PoolSize überschreitet, wird er an den RejectedExecutionHandler übergeben, um die Aufgabe zu verarbeiten.

Wenn ThreadPoolExecutor einen neuen Thread erstellt, aktualisieren Sie die Statussteuerung des Thread-Pools über CAS.

#Parameter _

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)
  • corePoolSizeDie Anzahl der Kernthreads im Thread-Pool. Wenn eine Aufgabe übermittelt wird, erstellt der Thread-Pool einen neuen Thread, um die Aufgabe auszuführen, bis die aktuelle Anzahl der Threads gleich corePoolSize ist. Auch wenn es andere inaktive Threads gibt, die neue Aufgaben ausführen können , Threads werden weiterhin erstellt; wenn die aktuelle Anzahl der Threads corePoolSize ist und die weiterhin übermittelten Aufgaben in der Blockierungswarteschlange gespeichert werden und auf ihre Ausführung warten; wenn die prestartAllCoreThreads()-Methode des Thread-Pools ausgeführt wird , erstellt und startet der Thread-Pool alle Kern-Threads im Voraus.

  • workQueueEine blockierende Warteschlange, in der Aufgaben gespeichert werden, die auf ihre Ausführung warten.

    • ArrayBlockingQueue: Eine begrenzte Blockierungswarteschlange basierend auf einer Array-Struktur, die Aufgaben nach FIFO sortiert;
    • LinkedBlockingQueue: Blockierungswarteschlange basierend auf der Struktur verknüpfter Listen, Sortieraufgaben nach FIFO, der Durchsatz ist normalerweise höher als bei ArrayBlockingQueue;
    • SynchronousQueue: Eine blockierende Warteschlange, in der keine Elemente gespeichert werden. Jeder Einfügevorgang muss warten, bis ein anderer Thread den Entfernungsvorgang aufruft. Andernfalls ist der Einfügevorgang immer blockiert und der Durchsatz ist normalerweise höher als bei LinkedBlockingQueue.
    • PriorityBlockingQueue: Unbegrenzte Blockierungswarteschlange mit Priorität;

LinkedBlockingQueueArrayBlockingQueueEs ist besser als die Leistung beim Einfügen und Löschen von Knoten, aber beide müssen beim Ausführen von Aufgaben gesperrt werden. put()Mithilfe eines sperrfreien Algorithmus wird die Ausführung anhand des Status des Knotens beurteilt, ohne dass Sperren verwendet werden. Der Kern ist .take()SynchronousQueueTransfer.transfer()

  • maximumPoolSize Die maximal zulässige Anzahl von Threads im Thread-Pool. Wenn die aktuelle Blockierungswarteschlange voll ist und die Aufgabe weiterhin übermittelt wird, wird ein neuer Thread zum Ausführen der Aufgabe erstellt, vorausgesetzt, die aktuelle Anzahl der Threads ist kleiner als die maximale Poolgröße. Wenn die Blockierungswarteschlange eine unbegrenzte Warteschlange ist, beträgt die maximale Poolgröße funktioniert nicht, da er nicht an den Kern-Thread-Pool übermittelt werden kann. Der Thread wird kontinuierlich in die workQueue gestellt.

  • keepAliveTime Die Überlebenszeit, wenn der Thread im Leerlauf ist, d. h. wenn der Thread keine Aufgaben zum Ausführen hat, überlebt der Thread weiterhin. Standardmäßig ist dieser Parameter nur nützlich, wenn die Anzahl der Threads größer als corePoolSize ist und die Zahl der inaktiven Threads diesen Wert überschreitet die Zeit wird beendet;

  • unit Einheit von keepAliveTime

  • threadFactory Erstellen Sie eine Factory für Threads. Mit einer benutzerdefinierten Thread-Factory können Sie für jeden neu erstellten Thread einen identifizierbaren Thread-Namen festlegen. Der Standardwert ist DefaultThreadFactory

  • handler Die Sättigungsstrategie des Thread-Pools. Wenn die Blockierungswarteschlange voll ist und keine inaktiven Arbeitsthreads vorhanden sind und Sie weiterhin Aufgaben senden, müssen Sie eine Strategie zur Verarbeitung der Aufgabe übernehmen. Der Thread-Pool bietet 4 Strategien:

    • AbortPolicy: Eine Ausnahme direkt auslösen, die Standardstrategie;
    • CallerRunsPolicy: Verwenden Sie den Thread, in dem sich der Aufrufer befindet, um die Aufgabe auszuführen.
    • DiscardOldestPolicy: Verwerfen Sie die oberste Aufgabe in der Blockierungswarteschlange und führen Sie die aktuelle Aufgabe aus.
    • DiscardPolicy: Aufgabe direkt verwerfen;

Natürlich können Sie die RejectedExecutionHandler-Schnittstelle auch entsprechend dem Anwendungsszenario implementieren und die Sättigungsstrategie anpassen, z. B. Protokollierung oder dauerhaftes Speichern von Aufgaben, die nicht verarbeitet werden können.

drei Typen

newFixedThreadPool

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

Nachdem die Anzahl der Threads im Thread-Pool corePoolSize erreicht hat, werden die Threads nicht freigegeben, auch wenn der Thread-Pool keine ausführbaren Aufgaben enthält.

Die Arbeitswarteschlange von FixedThreadPool ist eine unbegrenzte Warteschlange LinkedBlockingQueue (die Warteschlangenkapazität ist Integer.MAX_VALUE), was die folgenden Probleme verursacht:

  • Die Anzahl der Threads im Thread-Pool überschreitet corePoolSize nicht, was dazu führt, dass MaximumPoolSize und keepAliveTime nutzlose Parameter sind
  • Aufgrund der Verwendung unbegrenzter Warteschlangen wird FixedThreadPool niemals ablehnen, was bedeutet, dass die Sättigungsstrategie fehlschlägt

 newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

Es gibt nur einen Thread im initialisierten Thread-Pool. Wenn der Thread abnormal endet, wird ein neuer Thread neu erstellt, um die Ausführung der Aufgabe fortzusetzen. Der einzige Thread kann die Reihenfolge der Ausführung der übermittelten Aufgaben garantieren.

Aufgrund der Verwendung unbegrenzter Warteschlangen wird SingleThreadPool niemals abgelehnt, was bedeutet, dass die Sättigungsstrategie fehlschlägt

 newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
}

Die Anzahl der Threads im Thread-Pool kann Integer.MAX_VALUE erreichen, also 2147483647, und SynchronousQueue wird intern als Blockierungswarteschlange verwendet. Im Gegensatz zum von newFixedThreadPool erstellten Thread-Pool gibt newCachedThreadPool automatisch Thread-Ressourcen frei, wenn keine Aufgabe auszuführen ist Die Leerlaufzeit des Threads überschreitet keepAliveTime. Wenn beim Senden einer neuen Aufgabe kein Leerlaufthread vorhanden ist, verursacht das Erstellen eines neuen Threads zum Ausführen der Aufgabe einen gewissen Systemaufwand. Der Ausführungsprozess unterscheidet sich geringfügig von den beiden vorherigen:

  • Der Hauptthread ruft die offer()-Methode der SynchronousQueue auf und fügt sie in die Aufgabe ein. Wenn sich zu diesem Zeitpunkt ein inaktiver Thread im Thread-Pool befindet, der versucht, die Aufgabe der SynchronousQueue zu lesen, d. h. poll() der SynchronousQueue aufgerufen wird, dann übergibt der Haupt-Thread die Aufgabe an den Leerlauf-Thread. Andernfalls führen Sie (2) aus.
  • Wenn der Thread-Pool leer ist oder keine inaktiven Threads vorhanden sind, erstellen Sie einen neuen Thread, um die Aufgabe auszuführen.
  • Wenn der Thread, der die Aufgabe abgeschlossen hat, innerhalb von 60 Sekunden immer noch inaktiv ist, wird er beendet. Daher enthält der CachedThreadPool, der längere Zeit im Leerlauf war, keine Thread-Ressourcen.

Schließen Sie den Thread-Pool

Durchlaufen Sie alle Threads im Thread-Pool und rufen Sie nacheinander die Interrupt-Methode des Threads auf, um den Thread zu unterbrechen.

Herunterfahrmethode - Herunterfahren

Setzen Sie den Thread-Status im Thread-Pool auf den Status SHUTDOWN und unterbrechen Sie dann alle Threads, die keine Aufgaben ausführen.

Shutdown-Methode – ShutdownNow

Setzen Sie den Thread-Status im Thread-Pool auf den STOP-Status und stoppen Sie dann alle Threads, die Aufgaben ausführen oder anhalten. Rufen Sie einfach eine dieser beiden Shutdown-Methoden auf, isShutDown() gibt true zurück. Wenn alle Aufgaben erfolgreich geschlossen wurden, gibt isTerminated() zurück WAHR.

Detaillierter Quellcode von ThreadPoolExecutor

ein paar Schlüsselattribute

//这个属性是用来存放 当前运行的worker数量以及线程池状态的
//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,后29位拿来充当当前运行worker的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;

internen Zustand

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

Unter diesen ist die AtomicInteger-Variable ctl sehr leistungsfähig: Verwenden Sie die unteren 29 Bits, um die Anzahl der Threads im Thread-Pool anzuzeigen, und verwenden Sie die oberen 3 Bits, um den Betriebsstatus des Thread-Pools anzuzeigen:

  • RUNNING: -1 << COUNT_BITS, dh die oberen 3 Bits sind 111. Der Thread-Pool in diesem Zustand empfängt neue Aufgaben und verarbeitet Aufgaben in der Blockierungswarteschlange.
  • SHUTDOWN: 0 << COUNT_BITS, das heißt, die oberen 3 Bits sind 000, der Thread-Pool empfängt in diesem Zustand keine neuen Aufgaben, sondern verarbeitet Aufgaben in der Blockierwarteschlange;
  • STOP: 1 << COUNT_BITS, das heißt, die oberen 3 Bits sind 001, Threads in diesem Zustand empfangen keine neuen Aufgaben, verarbeiten auch keine Aufgaben in der Blockierungswarteschlange und unterbrechen laufende Aufgaben.
  • TIDYING: 2 << COUNT_BITS, das heißt, die oberen 3 Bits sind 010, alle Aufgaben wurden beendet;
  • TERMINATED: 3 << COUNT_BITS, das heißt, die oberen 3 Bits sind 011, die Methode „terminated()“ wurde ausgeführt

# Ausführung von Aufgaben

ausführen –> addWorker –>runworker (getTask)

Der Arbeitsthread des Thread-Pools wird von der Woker-Klasse implementiert. Unter der Garantie der ReentrantLock-Sperre wird die Woker-Instanz in das HashSet eingefügt und der Thread im Woker gestartet. Aus der Implementierung der Konstruktionsmethode der Woker-Klasse geht hervor, dass die Thread-Factory beim Erstellen eines Thread-Threads diesen als Parameter an die Woker-Instanz selbst übergibt. Wenn die Startmethode ausgeführt wird, um den Thread-Thread zu starten, Das Wesentliche besteht darin, die runWorker-Methode des Workers auszuführen. Nachdem die Ausführung von firstTask abgeschlossen ist, wird die wartende Aufgabe über die getTask-Methode aus der Blockierungswarteschlange abgerufen. Wenn sich keine Aufgabe in der Warteschlange befindet, wird die getTask-Methode blockiert und angehalten und belegt keine CPU-Ressourcen.

# Methode „execute()“.

ThreadPoolExecutor.execute(task)Executor.execute(task)

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {  
    //workerCountOf获取线程池的当前线程数;小于corePoolSize,执行addWorker创建新线程执行command任务
       if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // double check: c, recheck
    // 线程池处于RUNNING状态,把提交的任务成功放入阻塞队列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // recheck and if necessary 回滚到入队操作前,即倘若线程池shutdown状态,就remove(command)
        //如果线程池没有RUNNING,成功从阻塞队列中删除任务,执行reject方法处理任务
        if (! isRunning(recheck) && remove(command))
            reject(command);
        //线程池处于running状态,但是没有线程,则创建线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 往线程池中创建新的线程失败,则reject任务
    else if (!addWorker(command, false))
        reject(command);
}
  • Warum müssen Sie den Status des Thread-Pools noch einmal überprüfen?

In einer Umgebung mit mehreren Threads ändert sich der Status des Thread-Pools ständig, und ctl.get () ist eine nicht atomare Operation. Es ist sehr wahrscheinlich, dass sich der Status des Thread-Pools unmittelbar nach dem Status von ändert Der Thread-Pool wird abgerufen. Die Beurteilung, ob der Befehl zur Workque hinzugefügt werden soll, ist der Status vor dem Thread-Pool. Wenn keine doppelte Prüfung erfolgt und sich der Thread-Pool in einem nicht ausgeführten Zustand befindet (was in einer Multithread-Umgebung wahrscheinlich der Fall ist), wird der Befehl nie ausgeführt.

# addWorker-Methode

Aus der Implementierung der Ausführungsmethode können wir ersehen, dass addWorker hauptsächlich für die Erstellung neuer Threads und die Ausführung von Aufgaben verantwortlich ist. Wenn der Thread-Pool neue Threads erstellt, um Aufgaben auszuführen, muss er eine globale Sperre erwerben:

private final ReentrantLock mainLock = new ReentrantLock();
private boolean addWorker(Runnable firstTask, boolean core) {
    // CAS更新线程池数量
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            // 线程池重入锁
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();  // 线程启动,执行任务(Worker.thread(firstTask).start());
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

# Die Runworker-Methode der Worker-Klasse

 private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
     Worker(Runnable firstTask) {
         setState(-1); // inhibit interrupts until runWorker
         this.firstTask = firstTask;
         this.thread = getThreadFactory().newThread(this); // 创建线程
     }
     /** Delegates main run loop to outer runWorker  */
     public void run() {
         runWorker(this);
     }
     // ...
 }
  • Durch das Erben der AQS-Klasse ist es praktisch, den Aussetzungsvorgang des Arbeitsthreads zu realisieren.
  • Implementieren Sie die Runnable-Schnittstelle, die sich selbst als Aufgabe im Worker-Thread ausführen kann;
  • Die aktuell übermittelte Aufgabe firstTask wird als Parameter an den Konstruktor von Worker übergeben;

Einige Eigenschaften haben auch Konstruktoren:

//运行的线程,前面addWorker方法中就是直接通过启动这个线程来启动这个worker
final Thread thread;
//当一个worker刚创建的时候,就先尝试执行这个任务
Runnable firstTask;
//记录完成任务的数量
volatile long completedTasks;

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    //创建一个Thread,将自己设置给他,后面这个thread启动的时候,也就是执行worker的run方法
    this.thread = getThreadFactory().newThread(this);
}   

Die runWorker-Methode ist der Kern des Thread-Pools:

  • Nachdem der Thread gestartet wurde, wird die Sperre über die Entsperrmethode aufgehoben und der Status von AQS auf 0 gesetzt, was darauf hinweist, dass der Vorgang unterbrochen werden kann.
  • Der Worker führt firstTask aus oder ruft Aufgaben von workQueue ab:
    • Führen Sie Sperrvorgänge durch, um sicherzustellen, dass Threads nicht von anderen Threads unterbrochen werden (es sei denn, der Thread-Pool wird unterbrochen).
    • Überprüfen Sie den Thread-Pool-Status. Wenn der Thread-Pool unterbrochen ist, wird der aktuelle Thread unterbrochen.
    • vorExecute ausführen
    • Die Ausführungsmethode zum Ausführen der Aufgabe
    • Führen Sie die Methode afterExecute aus
    • Betrieb entsperren

Rufen Sie die wartende Aufgabe über die getTask-Methode aus der blockierenden Warteschlange ab. Wenn sich keine Aufgabe in der Warteschlange befindet, wird die getTask-Methode blockiert und angehalten und belegt keine CPU-Ressourcen.

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 先执行firstTask,再从workerQueue中取task(getTask())

        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

# getTask-Methode

Werfen wir einen Blick auf die Methode getTask(), die die Verwendung von keepAliveTime beinhaltet. Anhand dieser Methode können wir sehen, wie der Thread-Pool den Teil des Workers zerstört, der die corePoolSize überschreitet.

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

Beachten Sie, dass dieser Code der Schlüssel dafür ist, dass AliveTime funktioniert:

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();

AllowCoreThreadTimeOut ist falsch, der Thread wird nicht zerstört, selbst wenn er inaktiv ist; wenn er wahr ist, wird er zerstört, wenn er innerhalb von keepAliveTime noch inaktiv ist.

Wenn der Thread im Leerlauf warten darf, ohne zerstört zu werden, timed == false, workQueue.take task: Wenn die blockierende Warteschlange leer ist, wird der aktuelle Thread angehalten und gewartet; wenn eine Aufgabe zur Warteschlange hinzugefügt wird, wird der Thread leer sein aufgewacht, und die Take-Methode gibt die Aufgabe zurück und führt sie aus;

Wenn der Thread keinen endlosen Leerlauf zulässt, timed == true, workQueue.poll-Aufgabe: Wenn die blockierende Warteschlange innerhalb der keepAliveTime-Zeit immer noch keine Aufgaben hat, wird null zurückgegeben.

 Abgabe von Aufgaben

  1. Senden Sie die Aufgabe und warten Sie, bis der Thread-Pool ausgeführt wird
  2. Wenn die get-Methode der FutureTask-Klasse ausgeführt wird, wird der Hauptthread in einen WaitNode-Knoten gekapselt und in der Waiter-Liste gespeichert und blockiert, um auf das laufende Ergebnis zu warten.
  3. Nachdem die Ausführung der FutureTask-Aufgabe abgeschlossen ist, setzen Sie den den Kellnern entsprechenden WaitNode über UNSAFE auf Null und aktivieren Sie den Hauptthread über die Unpark-Methode der LockSupport-Klasse.
public class Test{
    public static void main(String[] args) {

        ExecutorService es = Executors.newCachedThreadPool();
        Future<String> future = es.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "future result";
            }
        });
        try {
            String result = future.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

In tatsächlichen Geschäftsszenarien erscheinen Future und Callable grundsätzlich paarweise, Callable ist für die Generierung von Ergebnissen und Future für die Erzielung von Ergebnissen verantwortlich.

  1. Die Callable-Schnittstelle ähnelt Runnable, außer dass Runnable keinen Wert zurückgibt.
  2. Wenn eine Ausnahme auftritt, werden nicht nur die normalen Ergebnisse der aufrufbaren Aufgabe zurückgegeben, sondern auch die Ausnahme zurückgegeben, dh die Zukunft kann verschiedene Ergebnisse der asynchronen Ausführungsaufgabe erhalten.
  3. Die Future.get-Methode führt dazu, dass der Hauptthread blockiert, bis die Callable-Aufgabe ausgeführt wird.

# Submit-Methode

AbstractExecutorService.submit() implementiert ExecutorService.submit(), um den Rückgabewert nach der Ausführung zu erhalten, und ThreadPoolExecutor ist eine Unterklasse von AbstractExecutorService.submit(), sodass die Submit-Methode auch eine Methode von ThreadPoolExecutor ist.

// submit()在ExecutorService中的定义
<T> Future<T> submit(Callable<T> task);

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);
// submit方法在AbstractExecutorService中的实现
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    // 通过submit方法提交的Callable任务会被封装成了一个FutureTask对象。
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

Die über die Submit-Methode übermittelte aufrufbare Aufgabe wird in ein FutureTask-Objekt gekapselt. Senden Sie FutureTask über die Methode Executor.execute an den Thread-Pool, um auf die Ausführung zu warten. Die endgültige Ausführung ist die Ausführungsmethode von FutureTask.

FutureTask-Objekt

public class FutureTask<V> implements RunnableFuture<V>FutureTask kann zur Ausführung an den Thread-Pool übermittelt werden (ausgeführt durch die Run-Methode von FutureTask).

  • internen Zustand
/* The run state of this task, initially NEW. 
    * ...
    * Possible state transitions:
    * NEW -> COMPLETING -> NORMAL
    * NEW -> COMPLETING -> EXCEPTIONAL
    * NEW -> CANCELLED
    * NEW -> INTERRUPTING -> INTERRUPTED
    */
private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

Die Änderung des internen Status wird durch sun.misc.Unsafe geändert

  • get-Methode
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
} 

Der Hauptthread wird intern über die Methode „awaitDone“ blockiert. Die spezifische Implementierung lautet wie folgt:

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)
            q = new WaitNode();
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}
  1. Wenn der Hauptthread unterbrochen wird, wird eine Interrupt-Ausnahme ausgelöst.
  2. Bestimmen Sie den aktuellen Status der FutureTask. Wenn dieser größer als COMPLETING ist, bedeutet dies, dass die Aufgabe ausgeführt wurde, und kehren Sie direkt zurück.
  3. Wenn der aktuelle Status gleich COMPLETING ist, bedeutet dies, dass die Aufgabe ausgeführt wurde. Zu diesem Zeitpunkt muss der Hauptthread nur CPU-Ressourcen über die Yield-Methode bereitstellen und warten, bis der Status NORMAL wird.
  4. Kapseln Sie den aktuellen Thread über die WaitNode-Klasse und fügen Sie ihn über UNSAFE zur Warteliste hinzu.
  5. Schließlich wird der Thread durch den Park oder parkNanos von LockSupport angehalten;

run-Methode

public void run() {
    if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

Die FutureTask.run-Methode wird im Thread-Pool ausgeführt, nicht im Haupt-Thread

  1. Durch Ausführen der Call-Methode der Callable-Aufgabe;
  2. Wenn der Aufruf erfolgreich ausgeführt wird, wird das Ergebnis über die Set-Methode gespeichert.
  3. Wenn bei der Aufrufausführung eine Ausnahme auftritt, speichern Sie die Ausnahme über setException.

Aufgabenabschluss

Die Shutdown-Methode setzt den Status des Thread-Pools auf SHUTDOWN. Nachdem der Thread-Pool in diesen Status gelangt ist, verweigert er die Annahme von Aufgaben und anschließend werden alle verbleibenden Aufgaben ausgeführt.

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //检查是否可以关闭线程
        checkShutdownAccess();
        //设置线程池状态
        advanceRunState(SHUTDOWN);
        //尝试中断worker
        interruptIdleWorkers();
            //预留方法,留给子类实现
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //遍历所有的worker
        for (Worker w : workers) {
            Thread t = w.thread;
            //先尝试调用w.tryLock(),如果获取到锁,就说明worker是空闲的,就可以直接中断它
            //注意的是,worker自己本身实现了AQS同步框架,然后实现的类似锁的功能
            //它实现的锁是不可重入的,所以如果worker在执行任务的时候,会先进行加锁,这里tryLock()就会返回false
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

ShutdownNow macht einen relativ guten Job. Es setzt zunächst den Thread-Pool-Status auf STOP und lehnt dann alle übermittelten Aufgaben ab. Unterbrechen Sie abschließend die links und rechts ausgeführten Worker und löschen Sie dann die Aufgabenwarteschlange.

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        //检测权限
        advanceRunState(STOP);
        //中断所有的worker
        interruptWorkers();
        //清空任务队列
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //遍历所有worker,然后调用中断方法
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

 tieferes Verständnis

Warum darf der Thread-Pool keine Executoren zum Erstellen verwenden? Was ist die empfohlene Methode?

Thread-Pools dürfen nicht mit Executoren, sondern mit ThreadPoolExecutor erstellt werden. Diese Verarbeitungsmethode ermöglicht es Schülern, die schreiben, sich klarer über die Ausführungsregeln des Thread-Pools zu informieren und das Risiko einer Ressourcenerschöpfung zu vermeiden. Beschreibung: Die Nachteile jeder Executor-Methode:

  • newFixedThreadPool und newSingleThreadExecutor: Das Hauptproblem besteht darin, dass die angesammelte Anforderungsverarbeitungswarteschlange sehr viel Speicher oder sogar OOM verbrauchen kann.
  • newCachedThreadPool und newScheduledThreadPool: Das Hauptproblem besteht darin, dass die maximale Anzahl von Threads Integer.MAX_VALUE beträgt, wodurch eine sehr große Anzahl von Threads oder sogar OOM erstellt werden kann.

 Empfohlener Weg 1

Zuerst eingeführt: commons-lang3-Paket

ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

#empfohlene Methode 2

Zuerst eingeführt: com.google.guava-Paket

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

// excute
pool.execute(()-> System.out.println(Thread.currentThread().getName()));

 //gracefully shutdown
pool.shutdown();

#empfohlener Weg 3

Spring-Konfigurations-Thread-Pool-Methode: Die benutzerdefinierte Thread-Factory-Bean muss ThreadFactory implementieren. Sie können auf andere Standardimplementierungsklassen dieser Schnittstelle verweisen und diese verwenden, um die Bean direkt zu injizieren und die Methode „execute“ (ausführbare Aufgabe) aufzurufen

    <bean id="userThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="2000" />

    <property name="threadFactory" value= threadFactory />
        <property name="rejectedExecutionHandler">
            <ref local="rejectedExecutionHandler" />
        </property>
    </bean>
    
    //in code
    userThreadPool.execute(thread);

 Bei der Konfiguration des Thread-Pools zu berücksichtigende Faktoren

Aus den vier Perspektiven Aufgabenpriorität, Aufgabenausführungszeit, Aufgabenart (CPU-intensiv/IO-intensiv) und Aufgabenabhängigkeiten. Und nutzen Sie begrenzte Arbeitswarteschlangen so nah wie möglich.

Aufgaben unterschiedlicher Art können mithilfe unterschiedlich großer Thread-Pools separat bearbeitet werden:

  • CPU-intensiv: so wenig Threads wie möglich, Ncpu+1
  • E/A-intensiv: So viele Threads wie möglich, Ncpu*2, z. B. Datenbankverbindungspool
  • Hybrid: CPU-intensive Aufgaben und E/A-intensive Aufgaben unterscheiden sich kaum in der Ausführungszeit und werden in zwei Thread-Pools aufgeteilt. Andernfalls ist keine Aufteilung erforderlich.

 Überwachen Sie den Status des Thread-Pools

Die folgenden Methoden von ThreadPoolExecutor können verwendet werden:

  • getTaskCount()Gibt die ungefähre Gesamtzahl der Aufgaben zurück, deren Ausführung jemals geplant war.
  • getCompletedTaskCount()Gibt die ungefähre Gesamtzahl der Aufgaben zurück, deren Ausführung abgeschlossen wurde. Gibt weniger Ergebnisse zurück als getTaskCount().
  • getLargestPoolSize()Gibt die größte Anzahl von Threads zurück, die sich jemals gleichzeitig im Pool befanden. Das zurückgegebene Ergebnis ist kleiner oder gleich MaximumPoolSize
  • getPoolSize()Gibt die aktuelle Anzahl der Threads im Pool zurück.
  • getActiveCount()Gibt die ungefähre Anzahl der Threads zurück, die aktiv Aufgaben ausführen.

 Referenzartikel

  • Die Kunst der gleichzeitigen Java-Programmierung
  • https://www.jianshu.com/p/87bff5cc8d8c
  • https://blog.csdn.net/programmer_at/article/details/79799267
  • https://blog.csdn.net/u013332124/article/details/79587436
  • https://www.journaldev.com/1069/threadpoolexecutor-java-thread-pool-example-executorservice

Ich denke du magst

Origin blog.csdn.net/a619602087/article/details/130527276
Empfohlen
Rangfolge