Heim > Java > javaLernprogramm > Hauptteil

Was ist ein Java-Thread-Pool? Detaillierte Erläuterung des Java-Thread-Pools

不言
Freigeben: 2018-09-19 15:13:35
Original
2122 Leute haben es durchsucht

Der Inhalt dieses Artikels befasst sich mit der Frage: Was ist der Java-Thread-Pool? Die detaillierte Erklärung des Java-Thread-Pools hat einen gewissen Referenzwert. Ich hoffe, dass sie für Sie hilfreich ist.

1. Was ist ein Thread-Pool:

java.util.concurrent.Executors bietet eine Implementierung der java.util.concurrent.Executor-Schnittstelle zum Erstellen eines Threads Pool

Die Multithreading-Technologie löst hauptsächlich das Problem der Ausführung mehrerer Threads in der Prozessoreinheit. Sie kann die Leerlaufzeit der Prozessoreinheit erheblich reduzieren und die Durchsatzkapazität der Prozessoreinheit erhöhen.

Angenommen, die Zeit, die ein Server zum Abschließen einer Aufgabe benötigt, beträgt: T1-Zeit zum Erstellen eines Threads, T2-Zeit zum Ausführen der Aufgabe im Thread und T3-Zeit zum Zerstören des Threads.

Wenn: T1 + T3 viel größer als T2 ist, können Sie einen Thread-Pool verwenden, um die Serverleistung zu verbessern.

Ein Thread-Pool umfasst die folgenden vier Grundkomponenten:

  • 1. Thread-Pool-Manager (ThreadPool): Wird zum Erstellen und Verwalten des Thread-Pools verwendet , einschließlich Thread-Pool erstellen, Thread-Pool zerstören, neue Aufgaben hinzufügen;

  • 2. Arbeitsthread (PoolWorker): Threads im Thread-Pool befinden sich in einem Wartezustand, wenn keine Aufgaben vorhanden sind. Zyklische Ausführung von Aufgaben;

  • 3. Aufgabenschnittstelle (Aufgabe): Die Schnittstelle, die jede Aufgabe implementieren muss, damit der Arbeitsthread die Ausführung der Aufgabe planen kann Eintrittspunkt der Aufgabe. Die Abschlussarbeit nach der Ausführung der Aufgabe, der Ausführungsstatus der Aufgabe usw. 4. Aufgabenwarteschlange (taskQueue): Wird zum Speichern nicht verarbeiteter Aufgaben verwendet . Stellen Sie einen Puffermechanismus bereit.

  • Die Thread-Pool-Technologie ist eine Technologie, die sich darauf konzentriert, die T1- und T3-Zeit zu verkürzen oder anzupassen und dadurch die Leistung von Serverprogrammen zu verbessern. Es ordnet T1 und T3 jeweils in den Start- und Endzeiträumen des Serverprogramms oder in einigen Leerlaufzeiträumen an, sodass bei der Verarbeitung von Kundenanfragen durch das Serverprogramm kein Overhead für T1 und T3 entsteht.

Der Thread-Pool passt nicht nur den Zeitraum an, in dem T1 und T3 generiert werden, sondern reduziert auch die Anzahl der erstellten Threads erheblich:

Nehmen Sie einen Server für einen Tag an. Es müssen 50.000 Anfragen bearbeitet werden, und für jede Anfrage ist ein separater Thread erforderlich. Im Thread-Pool ist die Anzahl der Threads im Allgemeinen festgelegt, sodass die Gesamtzahl der generierten Threads die Anzahl der Threads im Thread-Pool nicht überschreitet. Wenn der Server den Thread-Pool nicht zur Verarbeitung dieser Anforderungen verwendet, beträgt die Gesamtzahl Threads werden 50.000 sein. Die allgemeine Thread-Poolgröße beträgt deutlich weniger als 50.000. Daher verschwendet das Serverprogramm, das den Thread-Pool verwendet, keine Zeit mit der Verarbeitung von Anforderungen, um 50.000 zu erstellen, wodurch die Effizienz verbessert wird. 2. Gemeinsame Thread-Pools

①newSingleThreadExecutor

Ein einzelner Thread-Thread-Pool, das heißt, es arbeitet jeweils nur ein Thread im Thread-Pool und eine einzelne Thread-Zeichenfolge. Zeilenausführungsaufgaben

②newFixedThreadExecutor(n)

Eine feste Anzahl von Thread-Pools. Wenn eine Aufgabe nicht übermittelt wird, handelt es sich um einen Thread bis die maximale Anzahl von Thread-Pools erreicht ist, und dann später eingeben Die Warteschlange setzt die Ausführung erst fort, wenn die vorherige Aufgabe abgeschlossen ist

③newCacheThreadExecutor (empfohlen)

Cacheable Thread-Pool, Wenn die Thread-Pool-Größe die Verarbeitungsaufgabe überschreitet. Wenn die erforderlichen Threads benötigt werden, werden einige inaktive Threads (normalerweise 60 Sekunden lang nicht ausgeführt) recycelt. Wenn eine Aufgabe kommt, werden neue Threads verwendet intelligent zur Ausführung hinzugefügt.

④newScheduleThreadExecutor

Unbegrenzter Thread-Pool, der geplante und periodische Ausführungsthreads unterstützt

Der von Java bereitgestellte Thread-Pool ist meiner Meinung nach leistungsfähiger Wenn Sie das Funktionsprinzip des Thread-Pools verstehen, werden Sie sich beim Betrachten des Thread-Pools in der Klassenbibliothek nicht fremd fühlen.

Was ist ein Java-Thread-Pool? Detaillierte Erläuterung des Java-Thread-Pools

Was ist ein Java-Thread-Pool? Detaillierte Erläuterung des Java-Thread-PoolsArtikel 2:

Anleitung zur Verwendung des Java-Thread-PoolsEine Einführung

Die Verwendung von Threads nimmt in Java eine äußerst wichtige Position ein. In JDK1.4 und früheren JDK-Versionen gibt es viele Informationen dazu Thread-Pools Die Verwendung ist äußerst grob. Diese Situation hat sich nach jdk1.5 stark verändert. Nach Jdk1.5 wurde 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.

2: Thread-Pool

Die Rolle des Thread-Pools:

Die Funktion des Thread-Pools besteht darin, die Anzahl der Ausführungsthreads im System zu begrenzen.
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 Systemüberlastung und Ineffizienz. 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.

Warum Thread-Pool verwenden:

1. Reduzieren Sie die Anzahl der Thread-Erstellung und -Zerstörung, jeder Arbeitsthread kann wiederverwendet werden und mehrere Aufgaben ausführen.

2. Sie können die Anzahl der Arbeitsthreads im Thread-Pool entsprechend der Kapazität des Systems anpassen, um zu verhindern, dass der Server aufgrund übermäßigen Speicherverbrauchs erschöpft wird (jeder Thread benötigt etwa 1 MB Speicher und der Thread startet). Je mehr Speicher verbraucht wird, desto mehr Speicher wird verbraucht und schließlich stürzt das System ab.

Die Schnittstelle der obersten Ebene des Thread-Pools in Java ist Executor, aber streng genommen ist Executor kein Thread-Pool, sondern lediglich ein Tool zum Ausführen von Threads. Die eigentliche Thread-Pool-Schnittstelle ist ExecutorService.

Einige der wichtigeren Kategorien:

Klassifizierung Funktion
分类 作用
ExecutorService 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
ExecutorService
Echte Thread-Pool-Schnittstelle.
ScheduledExecutorService kann Timer/TimerTask ähneln und Probleme lösen, die eine wiederholte Ausführung von Aufgaben erfordern.
ThreadPoolExecutor Die Standardimplementierung von ExecutorService.
ScheduledThreadPoolExecutor Erbt die ScheduledExecutorService-Schnittstellenimplementierung von ThreadPoolExecutor, einer Klassenimplementierung der periodischen Aufgabenplanung.

Die Konfiguration eines Thread-Pools ist relativ kompliziert. Insbesondere wenn das Prinzip des Thread-Pools nicht ganz klar ist, ist es sehr wahrscheinlich, dass der konfigurierte Thread-Pool nicht optimal ist. Daher werden in der Executors-Klasse einige statische Fabriken bereitgestellt. Generieren Sie einige häufig verwendete Thread-Pools.

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,

, 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.

Instanz

1: newSingleThreadExecutor

package com.thread;
 /* * 
  *通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class MyThread extends Thread { 

    @Override public void run() { // TODO Auto-generated method stub // super.run();
    System.out.println(Thread.currentThread().getName()+"正在执行....");
    } 
}
Nach dem Login kopieren
package com.thread; 
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; 
/* 
 * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class singleThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub //创建一个可重用固定线程数的线程池
        ExecutorService pool=Executors.newSingleThreadExecutor(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
 Thread t1=new MyThread();

        Thread t2=new MyThread();

        Thread t3=new MyThread();

        Thread t4=new MyThread();

        Thread t5=new MyThread(); //将线程放到池中执行;
 pool.execute(t1);

        pool.execute(t2);

        pool.execute(t3);

        pool.execute(t4);

        pool.execute(t5); //关闭线程池
 pool.shutdown();

    }

}
Nach dem Login kopieren

Ergebnis:

pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
Nach dem Login kopieren

2newFixedThreadPool

 package com.thread; 
import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors; 
/* * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class fixedThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub //创建一个可重用固定线程数的线程池
        ExecutorService pool=Executors.newFixedThreadPool(2); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
 Thread t1=new MyThread();

        Thread t2=new MyThread();

        Thread t3=new MyThread();

        Thread t4=new MyThread();

        Thread t5=new MyThread(); //将线程放到池中执行;
 pool.execute(t1);

        pool.execute(t2);

        pool.execute(t3);

        pool.execute(t4);

        pool.execute(t5); //关闭线程池
 pool.shutdown();

    }

}
Nach dem Login kopieren

Ergebnis:

pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-2正在执行....
Nach dem Login kopieren

3, newCachedThreadPool

package com.thread;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 /* 
 * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class cachedThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub //创建一个可重用固定线程数的线程池
        ExecutorService pool=Executors.newCachedThreadPool(); //创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
 Thread t1=new MyThread();

        Thread t2=new MyThread();

        Thread t3=new MyThread();

        Thread t4=new MyThread();

        Thread t5=new MyThread(); //将线程放到池中执行;
 pool.execute(t1);

        pool.execute(t2);

        pool.execute(t3);

        pool.execute(t4);

        pool.execute(t5); //关闭线程池
 pool.shutdown();

    }

}
Nach dem Login kopieren

Ergebnis:

pool-1-thread-2正在执行....
pool-1-thread-1正在执行....
pool-1-thread-3正在执行....
pool-1-thread-4正在执行....
pool-1-thread-5正在执行....
Nach dem Login kopieren

4, newScheduledThreadPool

package com.thread; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; 
/* * 通过实现Runnable接口,实现多线程
 * Runnable类是有run()方法的;
 * 但是没有start方法
 * 参考:
 * http://blog.csdn.net/qq_31753145/article/details/50899119 * */

public class scheduledThreadExecutorTest{ public static void main(String[] args) { // TODO Auto-generated method stub
 ScheduledThreadPoolExecutor exec =new ScheduledThreadPoolExecutor(1);
       exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间就触发异常
 @Override public void run() { // TODO Auto-generated method stub //throw new RuntimeException();
            System.out.println("===================");

        }}, 1000, 5000, TimeUnit.MILLISECONDS);  

       exec.scheduleAtFixedRate(new Runnable(){//每隔一段时间打印系统时间,证明两者是互不影响的
 @Override public void run() { // TODO Auto-generated method stub
 System.out.println(System.nanoTime());

        }}, 1000, 2000, TimeUnit.MILLISECONDS);

    }

}
Nach dem Login kopieren

Ergebnis:

===================
23119318857491
23121319071841
23123319007891
===================
23125318176937
23127318190359
===================
23129318176148
23131318344312
23133318465896
===================
23135319645812
Nach dem Login kopieren

Drei: Detaillierte Erklärung von ThreadPoolExecutor

Die Signatur der vollständigen Konstruktormethode von ThreadPoolExecutor lautet: ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)</runnable> .

corePoolSize – Die Anzahl der im Pool gespeicherten Threads, einschließlich inaktiver Threads.

maximumPoolSize – Die maximal zulässige Anzahl von Threads im Pool.

keepAliveTime – Wenn die Anzahl der Threads größer als der Kern ist, ist dies die maximale Zeit, die die überschüssigen inaktiven Threads auf neue Aufgaben warten, bevor sie beendet werden.

unit – Die Zeiteinheit des keepAliveTime-Parameters.

workQueue – Eine Warteschlange, in der Aufgaben vor der Ausführung gehalten werden. Diese Warteschlange enthält nur ausführbare Aufgaben, die von der Ausführungsmethode übermittelt wurden.

threadFactory – Die Factory, die vom Executor zum Erstellen neuer Threads verwendet wird.

handler – Handler, der verwendet wird, wenn die Ausführung aufgrund einer Überschreitung des Thread-Bereichs und der Warteschlangenkapazität blockiert wird.

ThreadPoolExecutor ist die zugrunde liegende Implementierung der Executors-Klasse.

Im JDK-Hilfedokument gibt es eine solche Passage:

„Es wird dringend empfohlen, dass Programmierer die bequemere ExecutorsFactory-MethodeExecutors.newCachedThreadPool() (unbegrenzter Thread-Pool) verwenden , kann automatisches Thread-Recycling sein), Executors.newFixedThreadPool(int) (Thread-Pool fester Größe) Executors.newSingleThreadExecutor() (einzelner Hintergrund-Thread)

Sie haben alle vordefinierte Einstellungen für die meisten Verwendungsszenarien "

Im Folgenden vorgestellt . Quellcode mehrerer Klassen:

ExecutorService newFixedThreadPool (int nThreads): Thread-Pool mit fester Größe.

Wie Sie sehen können, sind die Größen von corePoolSize und MaximumPoolSize gleich (eigentlich ist der Parameter MaximumPoolSize, wie später vorgestellt wird, bedeutungslos, wenn eine unbegrenzte Warteschlange verwendet wird) und die Namen der Einstellungstabellen von keepAliveTime und Einheit Was? -Es ist Zeit zu erkennen, dass du nicht am Leben bleiben willst! Die letzte BlockingQueue wählte LinkedBlockingQueue, die die Eigenschaft hat, dass sie unbegrenzt ist.

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

      }</runnable>
Nach dem Login kopieren

ExecutorService newSingleThreadExecutor(): Einzelner Thread

public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService   
                 (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>()));   
       }</runnable>
Nach dem Login kopieren

ExecutorService newCachedThreadPool(): Unbegrenzter Thread-Pool, kann automatisches Thread-Recycling durchführen

Diese Implementierung ist interessant. Der erste ist der unbegrenzte Thread-Pool, sodass wir feststellen können, dass MaximumPoolSize groß ist. Zweitens wird SynchronousQueue bei der Auswahl von BlockingQueue verwendet. Möglicherweise sind Sie mit dieser BlockingQueue etwas unbekannt. Einfach ausgedrückt: In dieser QUEUE muss jeder Einfügungsvorgang auf den entsprechenden Entfernungsvorgang eines anderen Threads warten.

 public static ExecutorService newCachedThreadPool() {
   return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<runnable>());   
    }</runnable>
Nach dem Login kopieren

Beginnen wir mit dem Parameter BlockingQueue Im JDK wurde tatsächlich sehr deutlich gemacht, dass es drei Arten von Warteschlangen gibt.

All BlockingQueue kann zum Übertragen und Halten übermittelter Aufgaben verwendet werden. Diese Warteschlange kann zur Interaktion mit der Poolgröße verwendet werden:

如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)

如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

queue上的三种类型。

排队有三种通用策略:

直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  

BlockingQueue的选择。

例子一:使用直接提交策略,也即SynchronousQueue。

首先SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。在这里不是核心线程便是新创建的线程,但是我们试想一样下,下面的场景。

我们使用一下参数构造ThreadPoolExecutor:

 new ThreadPoolExecutor( 2, 3, 30, TimeUnit.SECONDS, new  SynchronousQueue<runnable>(), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy());</runnable>
Nach dem Login kopieren

当核心线程已经有2个正在运行.

  1. 此时继续来了一个任务(A),根据前面介绍的“如果运行的线程等于或多于 corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。”,所以A被添加到queue中。

  2. 又来了一个任务(B),且核心2个线程还没有忙完,OK,接下来首先尝试1中描述,但是由于使用的SynchronousQueue,所以一定无法加入进去。

  3. 此时便满足了上面提到的“如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。”,所以必然会新建一个线程来运行这个任务。

  4. 暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第一个添加入queue中,后一个呢?queue中无法插入,而线程数达到了maximumPoolSize,所以只好执行异常策略了。

所以在使用SynchronousQueue通常要求maximumPoolSize是无界的,这样就可以避免上述情况发生(如果希望限制就直接使用有界队列)。对于使用SynchronousQueue的作用jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。

什么意思?如果你的任务A1,A2有内部关联,A1需要先运行,那么先提交A1,再提交A2,当使用SynchronousQueue我们可以保证,A1必定先被执行,在A1么有被执行前,A2不可能添加入queue中。

例子二:使用无界队列策略,即LinkedBlockingQueue

这个就拿newFixedThreadPool来说,根据前文提到的规则:

如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。那么当任务继续增加,会发生什么呢?

如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。OK,此时任务变加入队列之中了,那什么时候才会添加新线程呢?

如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。这里就很有意思了,可能会出现无法加入队列吗?不像SynchronousQueue那样有其自身的特点,对于无界队列来说,总是可以加入的(资源耗尽,当然另当别论)。换句说,永远也不会触发产生新的线程!corePoolSize大小的线程数会一直运行,忙完当前的,就从队列中拿任务开始运行。所以要防止任务疯长,比如任务运行的实行比较长,而添加任务的速度远远超过处理任务的时间,而且还不断增加,不一会儿就爆了。

例子三:有界队列,使用ArrayBlockingQueue。

这个是最为复杂的使用,所以JDK不推荐使用也有些道理。与上面的相比,最大的特点便是可以防止资源耗尽的情况发生。

举例来说,请看如下构造方法:

 new ThreadPoolExecutor( 2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<runnable>(2), new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy());</runnable>
Nach dem Login kopieren

假设,所有的任务都永远无法执行完。

对于首先来的A,B来说直接运行,接下来,如果来了C,D,他们会被放到queue中,如果接下来再来E,F,则增加线程运行E,F。但是如果再来任务,队列无法再接受了,线程数也到达最大的限制了,所以就会使用拒绝策略来处理。

keepAliveTime

jdk中的解释是:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

有点拗口,其实这个不难理解,在使用了“池”的应用中,大多都有类似的参数需要配置。比如数据库连接池,DBCP中的maxIdle,minIdle参数。

什么意思?接着上面的解释,后来向老板派来的工人始终是“借来的”,俗话说“有借就有还”,但这里的问题就是什么时候还了,如果借来的工人刚完成一个任务就还回去,后来发现任务还有,那岂不是又要去借?这一来一往,老板肯定头也大死了。

合理的策略:既然借了,那就多借一会儿。直到“某一段”时间后,发现再也用不到这些工人时,便可以还回去了。这里的某一段时间便是keepAliveTime的含义,TimeUnit为keepAliveTime值的度量。

RejectedExecutionHandler

另一种情况便是,即使向老板借了工人,但是任务还是继续过来,还是忙不过来,这时整个队伍只好拒绝接受了。

RejectedExecutionHandler接口提供了对于拒绝任务的处理的自定方法的机会。在ThreadPoolExecutor中已经默认包含了4中策略,因为源码非常简单,这里直接贴出来。

CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) {

               r.run();

           }

       }
Nach dem Login kopieren

这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。

AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException

 这种策略直接抛出异常,丢弃任务。

DiscardPolicy:不能执行的任务将被删除

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

       }
Nach dem Login kopieren

 这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。

DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 
if (!e.isShutdown()) {

               e.getQueue().poll();

               e.execute(r);

           }

       }
Nach dem Login kopieren

该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。

设想:如果其他线程都还在运行,那么新来任务踢掉旧任务,缓存在queue中,再来一个任务又会踢掉queue中最老任务。

总结:

keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。

反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。

public static ExecutorService newFixedThreadPool(int nThreads) {
 return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>());
}</runnable>
Nach dem Login kopieren

Das obige ist der detaillierte Inhalt vonWas ist ein Java-Thread-Pool? Detaillierte Erläuterung des Java-Thread-Pools. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage