03. Detaillierte Erläuterung der Verwendung der ThreadPoolExecutor-Klasse des Thread-Pools

Wir müssen Threads erstellen, wenn sie verwendet werden, und sie zerstören, wenn sie nicht verwendet werden. Eine solche häufige Verwendung und Zerstörung von Threads kostet viel Zeit und Ressourcen. Wie können wir vermeiden, dass Threads häufig erstellt und zerstört werden, wenn Threads verwendet werden?

Dies erfordert die Thread-Pool-Technologie: Fügen Sie mehrere Threads in einen Pool ein, und der Pool verwaltet die Erstellung, Verwendung und Zerstörung dieser Threads.

ThreadPoolExecutor

Die Kernklasse des Thread-Pools: ThreadPoolExecutor.

1. Die Vererbungsbeziehung der übergeordneten Klasse von ThreadPoolExecutor:

1.ThreadPoolExecutor erbt AbstractExecutorService (abstrakte Klasse)

2. AbstractExecutorService (abstrakte Klasse), die die ExecutorService-Schnittstelle implementiert.

3.ExecutorService erbt die Executor-Schnittstelle

Executor ist eine Schnittstelle der obersten Ebene, in der nur eine Methode execute (Runnable) deklariert ist, der Rückgabewert ungültig ist und der Parameter vom Typ Runnable ist. Dies kann aus der Literalbedeutung verstanden werden, die zur Ausführung der Aufgabe verwendet wird übergeben;

Die ExecutorService-Schnittstelle erbt die Executor-Schnittstelle und deklariert einige Methoden: submit, invokeAll, invokeAny, shutDown usw.;

Die abstrakte Klasse AbstractExecutorService implementiert die ExecutorService-Schnittstelle und implementiert grundsätzlich alle im ExecutorService deklarierten Methoden.

2. Die Konstruktionsmethode von ThreadPoolExecutor:

public class ThreadPoolExecutor extends AbstractExecutorService {
    
    
   
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    
}

Einführung in die Parameter der Bauweise:

  • corePoolSize: Die Größe des Kernpools. Dieser Parameter hat eine gute Beziehung zum Implementierungsprinzip des später beschriebenen Thread-Pools. Nach dem Erstellen des Thread-Pools befindet sich standardmäßig kein Thread im Thread-Pool. Stattdessen wartet er auf das Eintreffen von Aufgaben, bevor er Threads zum Ausführen von Aufgaben erstellt, es sei denn, die Methoden prestartAllCoreThreads () oder prestartCoreThread () werden aufgerufen Aus dem Namen geht hervor, dass vorab erstellte Threads, dh CorePoolSize-Threads oder ein Thread, erstellt werden, bevor keine Aufgaben eintreffen. Standardmäßig beträgt die Anzahl der Threads im Thread-Pool nach dem Erstellen des Thread-Pools 0. Wenn eine Aufgabe eingeht, wird ein Thread erstellt, um die Aufgabe auszuführen. Wenn die Anzahl der Threads im Thread-Pool corePoolSize erreicht, wird dies der Fall sein Reichweite Die Aufgaben werden in die Cache-Warteschlange gestellt.
  • MaximumPoolSize: Die maximal zulässige Anzahl von Threads im Thread-Pool. Wenn die aktuelle Blockierungswarteschlange voll ist und die Aufgabe weiterhin gesendet wird, wird ein neuer Thread erstellt, um die Aufgabe auszuführen, vorausgesetzt, die aktuelle Anzahl von Threads ist kleiner als die maximalePoolSize. Wenn die blockierende Warteschlange eine unbegrenzte Warteschlange ist, wird die maximalePoolSize verwendet funktioniert nicht, da es nicht an den Core-Thread-Pool gesendet werden kann. Der Thread wird kontinuierlich in die workQueue eingefügt.
  • keepAliveTime: Gibt an, wie lange der Thread höchstens bleibt, wenn keine Taskausführung beendet wird. Standardmäßig funktioniert keepAliveTime nur, wenn die Anzahl der Threads im Thread-Pool größer als corePoolSize ist, bis die Anzahl der Threads im Thread-Pool nicht größer als corePoolSize ist, dh wenn die Anzahl der Threads im Thread-Pool größer ist als corePoolSize, wenn ein Thread inaktiv ist Wenn die Zeit keepAliveTime erreicht, wird er beendet, bis die Anzahl der Threads im Thread-Pool corePoolSize nicht überschreitet. Wenn jedoch die Methode allowCoreThreadTimeOut (boolean) aufgerufen wird und die Anzahl der Threads im Thread-Pool nicht größer als corePoolSize ist, funktioniert der Parameter keepAliveTime auch so lange, bis die Anzahl der Threads im Thread-Pool 0 beträgt.
  • unit: Die Zeiteinheit des Parameters keepAliveTime. Es gibt 7 Werte. Die TimeUnit-Klasse enthält 7 statische Eigenschaften:
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
 
  • workQueue: Eine Blockierungswarteschlange, in der Aufgaben gespeichert werden, die auf ihre Ausführung warten. Die Auswahl dieses Parameters ist ebenfalls sehr wichtig und hat erhebliche Auswirkungen auf den laufenden Prozess des Thread-Pools. Im Allgemeinen gibt es hier mehrere Optionen für die Blockierungswarteschlange ::
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

ArrayBlockingQueue und PriorityBlockingQueue werden seltener verwendet, und LinkedBlockingQueue und Synchronous werden im Allgemeinen verwendet. Die Warteschlangenstrategie des Thread-Pools hängt mit BlockingQueue zusammen.

  • threadFactory: Thread-Factory, die hauptsächlich zum Erstellen von Threads verwendet wird.
  • Handler: Gibt die Strategie mit den folgenden vier Werten an, wenn die Verarbeitung der Aufgabe abgelehnt wird:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

3. In der ThreadPoolExecutor-Klasse gibt es mehrere wichtige Methoden:

1.execute ()

Die execute () -Methode ist tatsächlich die in Executor deklarierte Methode, die in ThreadPoolExecutor implementiert ist. Diese Methode ist die Kernmethode von ThreadPoolExecutor. Über diese Methode kann eine Aufgabe an den Thread-Pool gesendet und vom Thread-Pool ausgeführt werden.

2.submit ()

Die submit () -Methode ist eine in ExecutorService deklarierte Methode. AbstractExecutorService verfügt bereits über eine bestimmte Implementierung. Sie wird in ThreadPoolExecutor nicht neu geschrieben. Diese Methode wird auch zum Übermitteln von Aufgaben an den Thread-Pool verwendet, aber die Methode execute und The unterscheidet sich. Es kann das Ergebnis der Taskausführung zurückgeben. Wenn Sie sich die Implementierung der submit () -Methode ansehen, werden Sie feststellen, dass es sich tatsächlich um die aufgerufene execute () -Methode handelt, die jedoch Future verwendet, um das Ergebnis der Taskausführung abzurufen.

3.shutdown () 和 shutdownNow ()

  • shutdown (): Der Thread-Pool wird nicht sofort beendet, sondern beendet, nachdem alle Aufgaben in der Task-Cache-Warteschlange ausgeführt wurden, aber keine neuen Aufgaben akzeptiert werden
  • shutdownNow (): Beenden Sie sofort den Thread-Pool und versuchen Sie, die ausgeführte Aufgabe zu unterbrechen. Löschen Sie die Task-Cache-Warteschlange und kehren Sie zu der Aufgabe zurück, die noch nicht ausgeführt wurde

In der ThreadPoolExecutor-Klasse ist die Übermittlungsmethode für die Kernaufgabe die execute () -Methode. Obwohl die Aufgabe auch über submit übergeben werden kann, ist der letzte Aufruf in der submit-Methode die execute () -Methode:

public void execute(Runnable command) {
    
    
    if (command == null)
        throw new NullPointerException();
    //1.获取当前正在运行的线程个数
    int c = ctl.get();
    //2.判断当前线程数是否小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
    
    
        //2.1 新建一个线程执行任务
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //3.核心池已满,但任务队列未满,添加到队列中
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        //任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command))
            //3.2如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
            reject(command);
        else if (workerCountOf(recheck) == 0)
            //3.3如果之前的线程已被销毁完,新建一个线程
            addWorker(null, false);
    }
    else if (!addWorker(command, false)) //4.核心池已满,队列已满,试着创建一个新线程
        reject(command);//如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

4. Liste der Hauptattribute der ThreadPoolExecutor-Klasse

private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
private volatile long  keepAliveTime;    //线程存活时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize; //线程池支持的最大线程数
 
private volatile int   poolSize;       //线程池中当前的线程数
 
private volatile RejectedExecutionHandler handler; //任务拒绝策略
 
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
 
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
 
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

5. Task-Cache-Warteschlange und Warteschlangenstrategie

Wir haben die Task-Cache-Warteschlange bereits mehrfach erwähnt, dh workQueue, in der Aufgaben gespeichert werden, die auf ihre Ausführung warten.

Der Typ von workQueue ist BlockingQueue, der normalerweise aus den folgenden drei Typen ausgewählt werden kann:

1) ArrayBlockingQueue: Array-basierte First-In-First-Out-Warteschlange. Die Größe dieser Warteschlange muss beim Erstellen angegeben werden.

2) LinkedBlockingQueue: First-In-First-Out-Warteschlange basierend auf der verknüpften Liste. Wenn die Warteschlangengröße beim Erstellen nicht angegeben wird, wird standardmäßig Integer.MAX_VALUE verwendet.

3) SynchronousQueue: Diese Warteschlange ist etwas Besonderes. Sie speichert keine gesendeten Aufgaben, sondern erstellt direkt einen neuen Thread, um neue Aufgaben auszuführen.

6. Strategie zur Ablehnung von Aufgaben

Wenn die Task-Cache-Warteschlange des Thread-Pools voll ist und die Anzahl der Threads im Thread-Pool die maximale Poolgröße erreicht, wird bei bevorstehenden Aufgaben die Strategie zur Zurückweisung von Aufgaben übernommen. In der Regel gibt es die folgenden vier Strategien:

  • ThreadPoolExecutor.AbortPolicy: Verwerfen Sie die Aufgabe und lösen Sie die RejectedExecutionException aus.
  • ThreadPoolExecutor.DiscardPolicy: verwirft auch Aufgaben, löst jedoch keine Ausnahmen aus.
  • ThreadPoolExecutor.DiscardOldestPolicy: Verwerfen Sie die erste Aufgabe in der Warteschlange und versuchen Sie dann erneut, die Aufgabe auszuführen (wiederholen Sie diesen Vorgang).
  • ThreadPoolExecutor.CallerRunsPolicy: Die Aufgabe wird vom Thread-Pool abgelehnt, und die Aufgabe wird vom Thread ausgeführt, der die execute-Methode aufruft.

7. Gängige Fabrikmethoden

Um die Verwendung von Thread-Pools zu vereinfachen, werden in Executors verschiedene Factory-Methoden für Thread-Pools bereitgestellt. Auf diese Weise müssen viele Anfänger nicht viel über ThreadPoolExecutor wissen. Sie müssen nur die Factory-Methoden von Executors direkt verwenden, um Thread zu verwenden Schwimmbad:

  • newFixedThreadPool: Diese Methode gibt eine feste Anzahl von Thread-Pools zurück. Die Anzahl der Threads bleibt unverändert. Wenn eine Aufgabe gesendet wird. Wenn der Thread-Pool frei ist, wird sie sofort ausgeführt. Wenn nicht, wird sie in einer Aufgabenwarteschlange angehalten. Warten auf die Ausführung des kostenlosen Threads.
  • newSingleThreadExecutor: Erstellen Sie einen Thread-Pool. Wenn dieser inaktiv ist, führen Sie ihn aus. Wenn kein inaktiver Thread vorhanden ist, wird er in der Task-Warteschlange angehalten.
  • newCachedThreadPool: Gibt einen Thread-Pool zurück, der die Anzahl der Threads entsprechend der tatsächlichen Situation anpassen kann, die maximale Anzahl von Threads nicht begrenzt. Wenn inaktive Threads verwendet werden, werden Aufgaben ausgeführt, und wenn keine Aufgaben vorhanden sind, werden keine Threads erstellt. Und jeder Leerlauffaden wird nach 60 Sekunden automatisch recycelt
  • newScheduledThreadPool: Erstellen Sie einen Thread-Pool, der die Anzahl der Threads angeben kann. Dieser Thread-Pool hat jedoch auch die Funktion, Aufgaben zu verzögern und regelmäßig auszuführen, ähnlich wie Timer.

7. So konfigurieren Sie die Größe des Thread-Pools angemessen

Wie die Größe des Thread-Pools angemessen konfiguriert werden kann, muss im Allgemeinen die Größe des Thread-Pools entsprechend dem Aufgabentyp konfigurieren:

1. Wenn es sich um eine CPU-intensive Aufgabe handelt, müssen Sie die CPU so weit wie möglich zusammendrücken, und der Referenzwert kann auf N CPU + 1 eingestellt werden

2. Wenn es sich um eine E / A-intensive Aufgabe handelt, kann der Referenzwert auf 2 * N CPU eingestellt werden
 
, um eine schöne Formel zu erhalten:

  • Optimale Anzahl von Threads = ((Thread-Wartezeit + Thread-CPU-Zeit) / Thread-CPU-Zeit) * CPU-Nummer

Beispiel: Unsere Server-CPU-Kernnummer beträgt 4 Kerne, eine Task-Thread-CPU benötigt 20 ms, das Warten auf Threads (Netzwerk-E / A, Festplatten-E / A) dauert 80 ms, dann die optimale Anzahl von Threads: (80 + 20) / 20 * 4 = 20. Das heißt, die optimale Anzahl von Threads ist auf 20 eingestellt.

Ich denke du magst

Origin blog.csdn.net/weixin_43828467/article/details/114323138
Empfohlen
Rangfolge