1なぜ、スレッドプールを適用します
まず、我々は、オペレーティング・システムにスレッドがあることを知っている貴重なリソースを手動に時間が自動的にその後、run()メソッドを実行しているスレッドを作成してシャットダウンした場合、私たちそれぞれの使用のように、次回は、手動で作成し使用する必要があり、そのオペレーティング・システムの問題ではないか、私たちのための一種であり、時間とリソースを無駄に、我々は資源を達成するために、タスクを実行した後に受信した他のタスクの実行を継続したスレッドの一部を維持するように選択することができるように再利用を、これらのスレッドは通常言う構成するスレッドプールを。それは、次のような多くの利点を、持って来ることができます。
スレッド資源の再利用を実装します。手動でスレッドをシャットダウンし、リソースの無駄を削減します。
ある程度は、応答速度を向上させます。手動で作成する代わりに、スレッドプールスレッドの許容範囲内で直接使用することができる(状態タスクを受け取るも参照)。
スレッドを管理しやすいです。スレッドが一緒に私たちの望ましい状態を達成するように、私たちは統一国家またはそのタイムアウトを設定することができ、また減らすことができますOOMを起こる、例えば、我々は原因操作どこかの定数にいくつかのミスを犯した場合ので、スレッドを作成、それがベンを折りたたむようにシステムの原因となりますが、私たちは、上限および受信することができるタスクの最大数ながらタスクを実行するためのスレッドプールのスレッドを設定することができれば、このような状況を回避するために非常に良いことができます。(もちろん、これは合理的な範囲内のスレッドやタスクの数のあなたの最大の数に基づいています)
-私たちは、スレッドのライフサイクルで(ライフサイクル上のスレッド別の私の記事の1を見てとることができることを知って正しい姿勢Javaスレッドの状態と閉スレッド、通常の実行中のスレッド)run()
、その後、最終的な状態に方法端を、スレッドプールをあるスレッドを実現する方法を実行し、他のタスクにそれを実行するために継続するタスクを完了した後に死亡入りませんか?
2スレッドプールのスレッドが再利用を実現する方法
実際には、我々はおそらく推測することができ、その内部は間違いなく使用while
し、我々はおそらくその内部を見て、タスクを実行するために、継続してループをどのように、最初に全てのルックのexecute()
方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 这些花里胡哨的可以不用管,大概逻辑就下方3.1节的线程池工作流程,关键的是找到线程的启动方法start()
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
// 其线程的启动方法就放在这里的addWorker方法中
if (addWorker(command, true))
return;
c = ctl.get();
}
// some code....
}
私たちは、より明確に見るためにいくつかのロジックの削除、その実装ロジック、にポイントを見ることができます:
private boolean addWorker(Runnable firstTask, boolean core) {
// some code...
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// code...
if (workerAdded) {
/*
* 可以看到线程是从这里开始启动的,我们找到t的根源,发现是Worker中的thread对象
* 而我们传进来的执行也被传入worker中
*/
t.start();
workerStarted = true;
}
}
} finally {
// ...
}
return workerStarted;
}
フォローアップworker
工法を参照してくださいthread
。この工場で作成されたスレッドを使用し、この方法は、作成することで、独自のパスの内側(内部クラスWorker
を実装するRunnable
方法を)
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
主題がされ、それ自体が呼び出すメソッドを呼び出す方法は、その後、上記を使用して呼び出した後、スレッドを開始します方法を、私たちはその内部実装を見てみましょう。thread
runnable
Worker
thread.start()
Worker
run()
t.start()
Worker
run()
// 调用runWorker()
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
try {
// while循环不断的从队列中获取任务执行,直到满足条件退出循环
while (task != null || (task = getTask()) != null) {
w.lock();
try {
// 默认空实现,可以重写此方法以便在线程执行前执行一些操作
beforeExecute(wt, task);
try {
// 直接调用task的run方法,而task就是我们传进来的runnable
task.run();
} catch (Exception x) {
thrown = x; throw x;
} finally {
// 同上,钩子方法
afterExecute(task, thrown);
}
}
}
// other code
}
大丈夫、ここで私たちは私たちと多重タスクを実行中のスレッドを達成するために、プールをスレッド化する方法を知っているだろうことはほとんど使用することだと思うようになったwhile
明示的にタスクを呼び出し、キューからジョブへのループの継続的なアクセスをrun()
一切キューが存在しなくなるまで、この方法をそれが空である(または他の誤差要因は、ループを終了します)。
どのようにスレッドプールの作業がある3
3.1スレッドプールのワークフロー
まず、最初のマップのスレッドプールの作業、作業図面に基づいてワークフロースレッドプールを理解するために、作業図面は、ヘルプをよく理解しているために、このスレッドプールを覚えています。
- 実行に
Executor.execute(runnable)
またはsubmit(runnable/callable)
時間、この時点では、プール内のスレッドを確認するスレッドの数はスレッドのコア数に達していない場合、は、「カーネルスレッドの使命を作成します。(スレッドプールが分かれていると理解することができる「カーネルスレッド」と「最大スレッド」スレッドのタイプの2種類が、「最大スレッドは」アイドル期間の後に自分自身をシャットダウンします、「カーネルスレッドの仕事に取得しようとされています)- あなたがスレッドのコア数に達した場合は、キューがいっぱいになって確認し、フルでない場合は、消費者のためのキューにタスクを。(スレッドプール内のタスクとスレッドが直接相互作用、通常はしないのブロッキングキューを維持し、キューに配置しようとするために、時間のタスクを、そしてスレッドがタスク実行キューからピックを統一しています)
- キューがいっぱいの場合は、その後、スレッドの最大数かどうかをチェックするスレッドの数がない場合は、その後、それ以外の場合は実行ポリシーを拒否し、「最大スレッドの使命を作成します。
3.2スレッドプールを作成する方法
私たちは、最初のクラスの下位レベル渡すThreadPoolExecutor
作成されます。
public class Test {
/* -----为了便于理解,可以把线程池中的类型分成两类——[核心线程]和[最大线程]------ */
/* -----可以直接看main方法的例子,再上来看这里参数的注释方便理解------ */
/**
* 核心线程数,线程池的核心线程到达这个数值之后接收任务便不再创建线程,
* 而是放入队列等待消费,直到队列填满
*/
private static final int CORE_POOL_SIZE = 1;
/**
* 最大线程数,当队列被填满时再接收新的任务的时候就会创建'最大线程'来缓解压力,
* '最大线程'在空闲一段时间后会消亡,具体的空闲时间取决于下方的KEEP_ALIVE_TIME,
* '最大线程'达到这个数值后便不再创建,举个例子,核心线程数为1,最大线程数为2,
* 那么核心线程的数量最多为1,'最大线程'的数量最多为1(最大线程数-核心线程数)
*/
private static final int MAXIMUM_POOL_SIZE = 2;
/** 最大线程的空间时间,'最大线程'空闲时间达到这个数值时消亡,时间单位为下个参数TimeUnit*/
private static final int KEEP_ALIVE_TIME = 60;
/** 空闲时间的计量单位*/
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
/**
* 任务队列,为阻塞队列,阻塞队列的特点是
* 1.调用take()方法时,若队列为空则进入阻塞状态而不是返回空
* 2.调用put()方法时,若队列已满则进入阻塞状态
* 阻塞队列可以分为数组队列和链表队列(区别大概就是List和Linked的区别),可以通过设定
* 边界值的方式来决定队列中最多可以容纳多少任务,如果超出则创建最大线程或者采取拒绝策略
* 如果设定了边界值则为有界队列,否则则为无界队列(无界队列容易引起OOM,队列的大小应根据需求制定)
*/
private static final BlockingQueue<Runnable> BLOCKING_QUEUE = new ArrayBlockingQueue<>(1);
/** 线程工厂,由该工厂产生执行任务的线程*/
private static final ThreadFactory THREAD_FACTORY = Executors.defaultThreadFactory();
/**
* 拒绝策略,当已达到最大线程并且队列已满的时候对新来任务的处理措施,分为四种,由ThreadPoolExecutor内部类实现
* 1、ThreadPoolExecutor.CallerRunsPolicy 当前线程来执行其任务,也就是说调用executor.execute()的线程执行,
而不是线程池额外提供线程执行
* 2、ThreadPoolExecutor.AbortPolicy 直接抛出RejectedExecutionException异常。
* 3、ThreadPoolExecutor.DiscardPolicy 直接丢弃任务,不会对新来的任务进行任何处理,也不会得到任何反馈。
* 4、ThreadPoolExecutor.DiscardOldestPolicy 丢弃队列中最老的任务(指的是队列的第一个任务,调用poll()方法将其丢弃),
然后重新调用executor.execute()方法
*/
private static final RejectedExecutionHandler REJECTED_EXECUTION_HANDLER = new ThreadPoolExecutor.AbortPolicy();
public static void main(String[] args) throws InterruptedException {
// 创建一个主要线程数为1,最大线程数为2,队列大小为1的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE_TIME,
TIME_UNIT,
BLOCKING_QUEUE,
THREAD_FACTORY,
REJECTED_EXECUTION_HANDLER);
// 此时线程池中没有任何线程,直接创建一个主要线程来执行
executor.execute(() -> {
try {
System.err.println("execute thread1:" + Thread.currentThread().getName());
// 睡眠1秒,验证下方的线程放入了队列中而不是再次创建线程
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 此时主要线程数已经达到最大,新来的任务放入队列中
executor.execute(() -> {
try {
System.err.println("execute thread2:" + Thread.currentThread().getName());
// 睡眠1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 再次接收任务时,由于队列满了尝试创建最大线程数来执行
executor.execute(() -> {
try {
System.err.println("execute thread3:" + Thread.currentThread().getName());
// 睡眠1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 线程池已经处于饱和状态,再次来任务时采取拒绝策略,这里采取的是直接报错
executor.execute(() -> {
try {
System.err.println("execute thread4:" + Thread.currentThread().getName());
// 睡眠1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
図は、結果:
実行の順序を見ることができるから、1-> 3-> 2いいえ4は、良好な再現がある3.1プロセススレッドプールの作業部は、カーネルスレッドを作成するスレッドは、コアスレッドが最大数を持っているタスクを実行する(ここで、1)新しいタスクが(サイズはキューに配置され1)、キューがいっぱいである、スレッドの最大数(この例でも、任意の実行するために、新しいタスクを作成しようと実行の最大スレッドがなく、キューの先頭よりも、タスクに新しいであることを示しているがタスクには、タスク4を実行しないように)、その後、スレッドプールは、新しいタスクが拒否ポリシーの処理に対応して来た場合、ここでエラーを選択し、飽和しています。
ここではスペースの不足の例他を拒否した戦略ではなく、あなたがこのマシン上で自分自身をテストすることができることを確認します。
3.3単純な話では、共通のスレッドプールを執行します
JUCがパッケージを提供してExecutors
すぐに、スレッドプールの5種類を作成する方法を提供するフレームワーク、 - 、newFixedThreadPool()
、、newSingleThreadExecutor()
及び(Java1.8後に追加され、ここでは一時的に、その後のサプリメントを紹介)。newCachedThreadPool()
newScheduledThreadPool()
newWorkStealingPool()
newFixedThreadPool(int n)
:作成スレッドのコア数とスレッドの最大数であるNと言うことであるスレッドプール、のみコアスレッドプールのスレッド、全く最大スレッドが存在しないであろう(最大容量スレッド=スレッドの最大数-スレッドのコア数)、その使用キューがあるLinkedBlockingQueue
任意のタスクを放棄しないという意味、アンバウンド形式のキュー、それがOOMを発生することがあり、これは一つの大きな欠点があります。ライン上で見て、でも最初の4つの本質が素早くスレッドプールの使用を作成することを忘れ提案を覚えておく必要はありませんされて
ThreadPoolExecutor
実現したが、そのパラメータの異なる塗りつぶしその実装は次のように、あなたが見ることができ、これらのパラメータは、その性質を決定します。public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
newSingleThreadExecutor()
:何も言うことの側面を実装、スレッドプールの上にあるnは1であるが、唯一の違いは、その実装の外とは、層を詰めることです。public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
この層の役割は書き換えることで
finalize()
容易にするための方法をJVM
回復時間をスレッドプールが閉じられていることを確認するために、finalize
そしてJVM
私が見ることができる前に別のものを回復JVMガベージコレクションの記事を。// 具体实现 static class FinalizableDelegatedExecutorService extends DelegatedExecutorService { FinalizableDelegatedExecutorService(ExecutorService executor) { super(executor); } // 重写了finalize()方法,保证线程池回收的时候先执行完剩下的任务 protected void finalize() { super.shutdown(); } }
newCachedThreadPool()
:バッファキュー、無コアスレッド、スレッドの最大数Integer.MAX_VALUE
、および特別の使用SynchronousQueue
キューは、このキューは実質ベースで収納スペースを取りません、タスクにスレッドプールに提出し、タスクがタスクを受信しない場合には、その後、時間がします入力してくださいブロックされ、ミッションを取るためにキューから共感スレッドが同じですが、スレッドの最大数はここのためにInteger.MAX_VALUE
アイドル状態のスレッドが存在しない場合は、スレッドが作成されています、それは、そのたびに新しいジョブ ** OOMにつながる可能性があります。public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, // 同步队列 new SynchronousQueue<Runnable>()); }
newScheduledThreadPool(int n
:のタイミングスレッドプール、スレッドのコア数をn、スレッドの最大数であるInteger.MAX_VALUE
遅延を使用して、キューDelayedWorkQueue
達成するために、タイミングを達成するには、タスクのために完全です。public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
では、「アリババオープン仕様、」も許可されていない、と述べたExecutors
スレッドプールを作成するには、上記の理由も大雑把に言えば、資源はにつながる可能性を制御するためには良いはありませんOOMの状況は。
[必須]スレッドプールを使用することはできません
Executors
作成するには、しかしによってThreadPoolExecutor
道、このアプローチは、資源の枯渇の危険性を回避するために、学生がより明示的な運用ルールのスレッドプールを作成することができます。
4まとめ
毎日のニーズに、我々は必然的に状況が同時に複数のタスクを必要と遭遇するスレッドが従来の手動方法を使用して作成された場合、あなたは、スレッドの使用を避けることができない、そして、我々のシステムの面で応用の一種であります我々はいくつかの固定ネジの再利用を維持することを検討することができますので、これらはスレッドプールのスレッドを構成し、資源の浪費、これらのスレッドの効果的な管理は、損失を低減し、ある程度、応答速度を向上させることができます。
私たちは、スレッドの呼び出しであることを知っているstart()
メソッドの実装が完了した後に死んでしまう、スレッドプールは、タスクを実行するために持っている方法ですか?私たちは、execute()
内部クラスメソッドの実現の鍵を見つけましたWorker
。Worker
それを実装する内部クラスであるRunnable
インターフェースは、植物であろうと、スレッド内のスレッドを作成Worker
自体は、渡す、スレッドを呼び出しstart()
起動する結合方法の方法を、およびでプロセスの使用である待ち行列から連続ループミッション取得タスク、その後、表示方法、順番に複数のタスクを実行するスレッドを実現しています。Worker
run()
Worker
run()
while
run()
その後、我々は直感的なグラフィカルなワークフロースレッドプールを参照して解釈するためのコードのスニペットを使っ使用ThreadPoolExecutor
嘘のパラメータの意味、およびワークフロー全体の簡単なシミュレーションを。
最後に素早くスレッドプールを作成するにはいくつかの方法について話しました、その本質は呼び出すことですThreadPoolExecutor
コンストラクタを、しかし異なるパラメータがそれらの異なる性質を決定、それはThreadPoolExecutor
他の人がライン上で見て、基本的です。それが明らかにした。またExecutors
、スレッドプールが登場しているの作成にOOMのリスクを、推奨されますThreadPoolExecutor
作成します。
記事が間違っている場合、私は助けポイントのうちに願っています。
私は、不当にもかかわらず、言わなければならない「Javaは世界で最高の言語です。」