Javaのインタビューでは、しかしそれに言及していないスレッドプールの知識は、頼まれなければならないが、周波数の出現は非常に高いです。数ながら、公共のビューでは、「新しい地平線番組、」読者のバックは、Javaのスレッドプールについての記事を書くように、メッセージは、ので、この部分パートは、ここにあり、これは、関連するソースコードを達成し、説明するために、Javaスレッドプールの原則に基づいて、というようになります。
スレッドプールとは何ですか
スレッドプールは、マルチスレッド処理フォームは、管理することが引き渡さタスクを実行するタスク処理スレッドプールのスレッドプールに提出される予定です。
マルチコアCPUリソースを最大限に活用するためには、アプリケーションは、マルチコアアプリケーションのパフォーマンスを最大限に活用、マルチスレッド並列/同時計算を使用します。
各要求がスレッドを破壊し、タスクを実行するためのスレッドを作成するために、再度実行された場合、サーバはリソースの無駄になります想像してみてください。高い同時実行の場合、あるいはサーバーのリソースが不足します。
スレッドプールの主な役割は、2倍:異なる要求間の重複が頻繁スレッドの作成と破棄せずにスレッドを使用、スレッドコンテキストを削減しながら、あまりにも多くのスレッドを作成しないように、プロセスのメモリ空間が不足し、オーバーヘッド削減し、制御のスレッドの数を制限しますスイッチング時間。
共通のインタビューの質問
- Javaスレッドプールとの原則の実現の利点についての話?
- Javaはすべてのオペランドのスレッドプールを提供し、どのように進めるには?
- 内部スレッドプールのメカニズムによると、異常とみなされるべき新しいタスクを、提出する際?
- スレッドプールのどのような作業キューを持っていますか?
- メモリはそれを舞い上がる引き起こす可能性がアンバウンド形式のキューのスレッドプールを使用しますか?
- いくつかの一般的なスレッドプールと使用のシナリオについての話?
スレッドプールの作成と使用
内蔵のバージョンJDK5スレッドプールの実装ThreadPoolExecutorの増加、スレッドプールの種類を作成するために、エグゼキューを提供しながら。執行は、以下の一般的なスレッドプールの作成方法で提供されています。
- newSingleThreadExecutor:シングルスレッドのスレッドプール。異常終了の結果ならば、提出の実行順序を保証するために、新しいものを作成します。
- newFixedThreadPool:固定サイズのスレッドプールを作成します。インクリメント最大に提出されたタスク、アップに応じたスレッドは変わりません。異常終了の結果ならば、新しいスレッドサプリメントを作成します。
- newCachedThreadPool:キャッシュされたスレッドプールを作成します。自動的にタスクに応じてスレッドを追加したり、リサイクル。
- newScheduledThreadPool:タスクを実行するための定期的かつ定期的な要件をサポートします。
- newWorkStealingPool:JDK8は、追加動的に作成し、複数のキューを使用することにより、所望のスレッドレベルの並列性に応じてオフ競争を減らす、基礎となるForkJoinPoolの使用が実現しました。小さなタスクの実行完了」の複数し、次いでこれらの結果をマージし、CPUは、プロセッサコアで並列実行の複数に、複数の「小さなタスク」にタスクを分割し、複数の利点を取ることができるという利点それはすることができます。
スレッドプールの複数のタイプが(「アリババJava開発仕様書」を参照)を作成し、通常は直接使用する開発者のための推奨されませんサポートするために、JDKでエグゼキュークラスを提供します。
エグゼキュータ・スレッド・プールを作成することができますが、ThreadPoolExecutorの方法により、このアプローチは、学生が資源の枯渇の危険性を回避するために、より明示的な運用ルールのスレッドプールを作成することができますされていません。
エグゼキュータの欠点の方法の一部:
- newFixedThreadPoolとnewSingleThreadExecutor主な問題は、非常に大きなメモリ、でもOOMを消費することが要求処理キューの蓄積です。
- newCachedThreadPoolとnewScheduledThreadPoolは:主な問題は、スレッドの非常に多く、でもOOMを作成することができ、スレッドの最大数にInteger.MAX_VALUEです。
一方、アリババJava開発標準を作成したスレッドプールの3種類が推奨します。
一実施形態では、コモン・lang3パッケージの導入。
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());复制代码
モード2はcom.google.guavaパッケージを導入しています。
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());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown复制代码
三つの方法、春のスレッドプールコンフィギュレーションモード:カスタムスレッド豆の植物はThreadFactoryを達成するために必要な、参照は呼び出しが実行(Runnableをタスク)メソッドをすることができ、直接注入豆を使用し、他のデフォルトの実装クラスのインターフェースであってもよいです。
<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);复制代码
ThreadPoolExecutorのコンストラクタ
上記推奨される方法に加えて、スレッドプールを作成するだけでなく、コンストラクタThreadPoolExecutorを介して直接スレッドプールを作成します。本質的に、上記の方法は、最終的にThreadPoolExecutorオブジェクトを作成し、その後、包装工程を積み重ね。
ThreadPoolExecutorは、いくつかのコンストラクタを提供し、我々は最終的に呼び出すコンストラクタを説明します。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// 省略代码
}复制代码
分析パラメータの中核的な役割を次のように
- corePoolSize:スレッドプール内のスレッドのコアの最大数。
- maximumPoolSize:スレッドスレッドプールサイズの最大数。
- keepAliveTimeが:非生存コアスレッドのスレッドプールサイズアイドル時間。
- 単位:アイドルスレッド生存時間単位。
- ワークキュー:ブロックキュータスクを保存します。
- threadFactory:新しいスレッドファクトリを作成し、すべてのスレッドが植物によって作成され、デフォルトの実装があります。
- ハンドラ:ポリシースレッドプールを拒否。
チェンプール拒否の方針
ポリシーを拒否するための最後のパラメータのRejectedExecutionHandlerコンストラクタは、スレッドプールを指定します。要求は、この時点でハンドルに現在進行中のタスク、およびシステムを来たとき、しかし、我々は、対応する戦略がサービス拒否で取る必要があります。
デフォルトでは、4つのタイプがあります。
- AbortPolicy戦略:戦略は適切に動作からシステムを防止するため、投げを指示します。
- CallerRunsPolicy戦略は:限り、スレッドプールが閉じていないと、呼び出し側のスレッドで直接政策は、現在のタスクが破棄されて実行されます。
- DiscardOleddestPolicy戦略:この戦略は、タスクが実行される最も古い要求を破棄し、再度、現在のジョブを提出しようとします。
- DiscardPolicy戦略:戦略は黙って廃棄作業は、任意の処理をせずに処理することはできません。
もちろん、デフォルトに加えて、戦略の4種類もカスタマイズビジネスニーズに基づいてポリシーを拒否することができます。あなたはThreadPoolExecutorのパラメータとしてオブジェクトを作成する際のRejectedExecutionHandlerインタフェースを実装すること。
次のようにばね統合コアCallerBlocksPolicyをカスタマイズするには、関連するコードです。
public class CallerBlocksPolicy implements RejectedExecutionHandler {
private static final Log logger = LogFactory.getLog(CallerBlocksPolicy.class);
private final long maxWait;
public CallerBlocksPolicy(long maxWait) {
this.maxWait = maxWait;
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (!executor.isShutdown()) {
try {
BlockingQueue<Runnable> queue = executor.getQueue();
if (logger.isDebugEnabled()) {
logger.debug("Attempting to queue task execution for " + this.maxWait + " milliseconds");
}
if (!queue.offer(r, this.maxWait, TimeUnit.MILLISECONDS)) {
throw new RejectedExecutionException("Max wait time expired to queue task");
} else {
if (logger.isDebugEnabled()) {
logger.debug("Task execution queued");
}
}
} catch (InterruptedException var4) {
Thread.currentThread().interrupt();
throw new RejectedExecutionException("Interrupted", var4);
}
} else {
throw new RejectedExecutionException("Executor has been shut down");
}
}
}复制代码
実行スレッドプール
ThreadPoolExecutorを作成したら、あなたがスレッドプールにタスクを送信する場合、通常はexecuteメソッドを使用します。次のように実行する方法のフローチャートが実行されます。
- スレッドプールの生存におけるカーネルスレッドの数はスレッドcorePoolSizeの数より少ない場合、提出されたタスクを処理するためのコアスレッドプールのスレッドが作成されます。
- スレッドプール内のスレッドのコア数がいっぱいになると、そのスレッドの数に等しいcorePoolSize、提出新しいタスクとなっている、それがワークキューは、実行のためにキューに入れられたタスクキューに入れられます。
- corePoolSizeを生き延びたスレッドプール内のスレッドの数が等しく、およびタスクキューワークキューが一杯になった場合は、スレッドの数は、そのスレッドの最大数がいっぱいであるかどうかを、maximumPoolSizeかどうかを決定するために、届かない場合は、提出した非コアタスクを実行するスレッドを作成します。
- スレッドの現在の数がmaximumPoolSizeに達し、だけでなく、新しいタスクが来た場合、ポリシー処理の使用を指示することを拒否しました。
ソースコード解析
ThreadPoolExecutorでJDK8で次の外観は、達成するために、ソースコードでメソッドを実行します。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 线程池本身的状态跟worker数量使用同一个变量ctl来维护
int c = ctl.get();
// 通过位运算得出当然线程池中的worker数量与构造参数corePoolSize进行比较
if (workerCountOf(c) < corePoolSize) {
// 如果小于corePoolSize,则直接新增一个worker,并把当然用户提交的任务command作为参数,如果成功则返回。
if (addWorker(command, true))
return;
// 如果失败,则获取最新的线程池数据
c = ctl.get();
}
// 如果线程池仍在运行,则把任务放到阻塞队列中等待执行。
if (isRunning(c) && workQueue.offer(command)) {
// 这里的recheck思路是为了处理并发问题
int recheck = ctl.get();
// 当任务成功放入队列时,如果recheck发现线程池已经不再运行了则从队列中把任务删除
if (! isRunning(recheck) && remove(command))
//删除成功以后,会调用构造参数传入的拒绝策略。
reject(command);
// 如果worker的数量为0(此时队列中可能有任务没有执行),则新建一个worker(由于此时新建woker的目的是执行队列中堆积的任务,
// 因此入参没有执行任务,详细逻辑后面会详细分析addWorker方法)。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果前面的新增woker,放入队列都失败,则会继续新增worker,此时线程池的状态是woker数量达到corePoolSize,阻塞队列任务已满
// 只能基于maximumPoolSize参数新建woker
else if (!addWorker(command, false))
// 如果基于maximumPoolSize新建woker失败,此时是线程池中线程数已达到上限,队列已满,则调用构造参数中传入的拒绝策略
reject(command);
}复制代码
上記のコードでaddWorker次のメソッド呼び出しを見て、ソースコードを解析して達成するために:
private boolean addWorker(Runnable firstTask, boolean core) {
// 这里有一段基于CAS+死循环实现的关于线程池状态,线程数量的校验与更新逻辑就先忽略了,重点看主流程。
//...
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 把指定任务作为参数新建一个worker线程
w = new Worker(firstTask);
// 这里是重点w.thread是通过线程池构造函数参数threadFactory生成的woker对象
// 也就是说这个变量t就是代表woker线程。绝对不是用户提交的线程任务firstTask。
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 加锁之后仍旧是判断线程池状态等一些校验逻辑。
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
// 把新建的woker线程放入集合保存,这里使用的是HashSet
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 然后启动woker线程
// 该变量t代表woker线程,会调用woker的run方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 如果woker启动失败,则进行一些善后工作,比如说修改当前woker数量等
addWorkerFailed(w);
}
return workerStarted;
}复制代码
addWorker方法は、主に行うことですコレクションに追加された新しいスレッドWoker、wokerを作成することです。ワーカークラスの実行方法に上記メソッド呼び出しで、最終的に実行runWorker方法。
// Woker类实现了Runnable接口
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// task就是Woker构造函数入参指定的任务,即用户提交的任务
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
//一般情况下,task都不会为空(特殊情况上面注释中也说明了),因此会直接进入循环体中
//这里getTask方法是要重点说明的,它的实现跟我们构造参数设置存活时间有关
//我们都知道构造参数设置的时间代表了线程池中的线程,即woker线程的存活时间,如果到期则回收woker线程,这个逻辑的实现就在getTask中。
//来不及执行的任务,线程池会放入一个阻塞队列,getTask方法就是去阻塞队列中取任务,用户设置的存活时间,就是
//从这个阻塞队列中取任务等待的最大时间,如果getTask返回null,意思就是woker等待了指定时间仍然没有
//取到任务,此时就会跳过循环体,进入woker线程的销毁逻辑。
while (task != null || (task = getTask()) != null) {
w.lock();
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 {
//这里设为null,也就是循环体再执行的时候会调用getTask方法
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//当指定任务执行完成,阻塞队列中也取不到可执行任务时,会进入这里,做一些善后工作,比如在corePoolSize跟maximumPoolSize之间的woker会进行回收
processWorkerExit(w, completedAbruptly);
}
}复制代码
タスクの実行処理wokerスレッドがまずに割り当てられている初期化を実行し、ロジックの破壊を入力して、まだタスクが指定された時間内で実行できない場合、実行が完了した後、ブロッキングキューから実行可能なタスクを取得しようとします。ここだけcorePoolSizeとmaximumPoolSizeがその部分のwokerを直接回復しました。
実行し、違いを提出
また、あなたは、タスクはこの方法はまた、提出方法を使用することができます実行実行するために使用することができます。主な違いは次のとおりです。実行興味のあるシーンの戻り値に適用する必要はありません、方法書を提出することは、シーンの戻り値に焦点を当てる必要に適用することができます。
例外処理
例外が発生したタスクを実行するとき、どのようにそれに対処するには?スレッド例外スレッドときに対処する方法を初めて目。
試してみることにより、タスクでは...キャッチ例外は、次のコードを捕獲し、処理されます。
Thread t = new Thread(() -> {
try {
System.out.println(1 / 0);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
});
t.start();复制代码
多くのスレッド場合は、デフォルトのタスク例外処理メカニズムを使用すると、たuncaughtExceptionHandler Threadクラスを通じてデフォルトのスレッドの例外処理メカニズムを設定することができ、同じです。
たuncaughtExceptionHandlerは、インターフェイスを実装し、呼び出しは#setUncaughtExceptionHandler(たuncaughtExceptionHandler)メソッドスレッド。あなたはグローバルなデフォルトの例外処理メカニズムを設定したい場合は、スレッド#のsetDefaultUncaughtExceptionHandler(たuncaughtExceptionHandler)メソッドを呼び出すことができます。
スレッドグループは、デフォルトの例外処理メカニズムは以下の通りである提供しています。
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}复制代码
ThreadPoolExecutor例外処理メカニズムのスレッドは同じです。一方、ThreadPoolExecutorはたuncaughtExceptionHandler例外処理の設定方法を提供します。次の例:
public class ThreadPool {
public static void main(String[] args) {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d")
.setUncaughtExceptionHandler(new LogUncaughtExceptionHandler())
.build();
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(() -> {
throw new RuntimeException("测试异常");
});
pool.shutdown();
}
static class LogUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("打印LogUncaughtExceptionHandler中获得的异常信息:" + e.getMessage());
}
}
}复制代码
しかし、たuncaughtExceptionHandler方法の使用は、タスクの実行方法を実行するためにのみ適用され、この方法は無効提出することに留意すべきです。タスクの実行がスローされた例外が返されます。今後のオブジェクトメソッドで受信し、その後、処理されてもよい提出してください。この方法では、違いの一つ実行し、方法を提出すると考えることができます。
一般的なスレッドプールのキュー
作業キューのスレッドプールには、次があります。
- ArrayBlockingQueue:有界キューを、バウンド形式のブロッキングキュー、FIFOの発注量を達成するために、配列を使用しています。
- LinkedBlockingQueueは:キューの容量は遮断リストキュー構造に基づいて、設定することができ、FIFOソーティングタスクは、容量が設定されていない場合、選択するように設定することができる、キューはInteger.MAX_VALUEでの非ブロック境界、最大長さは、通常スループット高いですArrayBlockingQueneに、newFixedThreadPoolは、このスレッドプールのキューを使用しています。
- DelayQueue:遅延キュー、キューは、遅延期間のタスク実行タイミングです。小規模から大規模な注文執行に指定された時間によると、または挿入に従ってキューを注文しました。newScheduledThreadPoolは、このスレッドプールのキューを使用しています。
- PriorityBlockingQueue:優先キューは、優先度を持つアンバウンド形式のブロッキングキューです。
- SynchronousQueue:同期キューは、ブロッキングキュー要素が格納されていない別のスレッドが削除操作を呼び出し、または挿入操作は、通常、高いLinkedBlockingQueneよりもスループットブロックされた状態、にあった、newCachedThreadPoolこのスレッドプールのキューを使用するまで、各挿入操作が待機しなければなりません。
スレッドプールを閉じます
スレッドプールを達成するためにshutdownNowのと二つの方法のシャットダウンを呼び出すことができます閉じます。
shutdownNowの:タスクであることに、すべての廃止のすべての割込み()、タスクの停止実行がまだ開始されていない実行を行わず起動するタスクリストに戻ります。
シャットダウン:我々はシャットダウンを呼び出すと、スレッドプールは、もはや新しいタスクを受け入れないでしょうが、提出されたか、タスクが実行されて終了することを強制されることはありません。
参考記事:
https://www.jianshu.com/p/5df6e38e4362
https://juejin.im/post/5d1882b1f265da1ba84aa676
オリジナルリンク:「インタビューの質問- Javaのスレッドプールについての記事が十分です。」