https://juejin.im/post/5b5f10d65188251ad06b78e3
1.キューは何ですか
私はあなたが私たちのすべてをチェックするために待機して、良いの行を立つ表示されます、我々は彼らにスーパーマーケットのレジに行き、私たちの実際の生活の中でどこでもキューイングするは見知らぬ人を聞かないようにキューと信じて、なぜ列に立って、あなたは、私たちは品質を持っていない想像スーパーマーケットの崩壊にするだけでなく法案を、アップ急いでなく、おそらく、我々はまた、頻繁に発生している現実には、実際に、当然のことながら、これらの事を殺到の多様性を引き起こします。
もちろん、コンピュータの世界では、キューはデータ構造の一種である、(最初の先出しで)FIFOキューで使用される、新しい要素(待ちキューに要素が)常に末尾に挿入され、常にとき頭から読み込まれ読み始めます。通常キューキューのために使用する計算に(生産者 - 消費者モデル)をデカップリングするために使用され、(例えば、キューが待機しているスレッドプール、ロックが待っているキューに入れられた)、などが挙げられる非同期。
キュー内2.jdk
JDKはキューに、キュー内java.util.Queueインターフェースは、2つのカテゴリに分類され、1はスレッドセーフではないのLinkedListをArrayDequeを達成しているというように、クラスはjava.util.concurrentパッケージの下にありますスレッドセーフに属し、当社の実際の環境では、我々のマシンはマルチスレッドであり、マルチスレッドの場合、同じ時間に操作をキューイングのためのキューは、データをカバーし、スレッドを使用して危険な存在になる場合、データの損失やその他の予測できません事、我々は唯一のスレッドセーフなキューを選択することができますので、この時間。単純部分キューが含まJDKセキュリティスレッドで提供される次のキュー:
キュー名 | かどうかは、ロックされました | データの構造 | 主要な技術のポイント | ロックがあります | コミュニティがあります |
---|---|---|---|---|---|
ArrayBlockingQueue | それはあります | 配列array | ReentrantLockの | ロック | 有界 |
LinkedBlockingQueue | それはあります | リスト | ReentrantLockの | ロック | 有界 |
転送キューのリンク | ノー | リスト | CAS | いいえロックません | 無制限 |
ConcurrentLinkedQueue | ノー | リスト | CAS | いいえロックません | 無制限 |
我々は、ロックフリーキューがアンバウンド形式である必要があり、キューが制限されているロック、見ることができ、これは問題があるだろう、私たちは本当のオンライン環境、アンバウンド形式のキューを持って、私たちのシステムへの影響は比較的大きいです、それは我々が最初に、役に立たないアンバウンド形式のキューではありませんもちろん、アンバウンド形式のキューを排除しなければならないので、メモリのオーバーフローに直接私たちを導くが、いくつかのシナリオでは除外しなければならないかもしれません。第二には、ArrayBlockingQueue、LinkedBlockingQueue 2つのキューを去った後、彼らは両方のスレッドセーフなReentrantLockの制御され、それらの二つの違いが配列され、リンクされたリストは、キューに、キュー要素は、すぐに一般的な意志を得る取得することです次の要素、またはキューの要素がより取得する可能性があり、かつ速度がわずかに良いアクセスとなりますので、メモリ内の配列のアドレスは、キャッシュ(キャッシュラインも下に紹介します)を最適化するオペレーティング・システムでは連続していますチップは、我々はArrayBlockingQueueを選択しようとします。そして、それは、このような早期のlog4j非同期として、多くのサードパーティの枠組みの中で判明、ArrayBlockingQueueを選択しています。
もちろんArrayBlockingQueueは、また、独自の欠点があり、パフォーマンスが相対的に低い、なぜJDKは、ロックフリーキューの数が増加します実際には、非常に動揺し、パフォーマンスを向上させるために、彼らはロックを必要としない、とコミュニティの必要性は、この時間は、私は怖いです、ない助けるが言うことができますなぜあなたは神をしませんか?しかし、それは本当に天国でした。
3.Disruptor
かく乱がそのその日を超えている、かく乱は、英国の外国為替会社は、高性能LMAXキューを開発し、並行処理、およびフレームワークプログラム2011Dukeのイノベーションアワードにアクセスするためのオープンソースのフレームワークです。ネットワークは、ロックケースキューの同時操作がない場合に達成することができ、かく乱は、毎秒600万件のオーダーをサポートすることができ、シングルスレッドシステムに基づいて開発します。現時点では、Apacheの嵐、キャメル、Log4j2ので、統合フレームワークの中でよく知られているなど、高い性能を達成するために、キューかく乱JDKを置き換えるために使用されています。
3.1なぜそんなに速いハードウェア?
すでにかく乱が爆破費やしたとして、あなたは不思議に思われる必要があり、彼は本当に持っている可能性があり、このようなNiubiそれ、私の答えは、もちろん、かく乱に殺された3があります。
- CAS
- フォルス・シェアリングを解消
- これら三つのリングバッファの殺害装置では、かく乱は非常に高速なハードウェアになります。
3.1.1ロックおよびCAS
ArrayBlockingQueue我々はヘビー級のロックロックで、私たちのプロセスにロック、我々はロックを一時停止しますので、アンロックのことを放棄して、スレッドは、このプロセスを再開しますいくつかのオーバーヘッドがあるでしょう、そしてなぜ私たちがロックを取得していないと、唯一のスレッドが何もできない、このスレッドを待っていることができます。
交換が設定されている場合、古い値は、馴染みの楽観的ロックは、CASは楽観的ロックを実装するために使用することができることを知っているどのスレッドをCASない場合CAS(比較およびスワップ)、名前が最初の交換を比較示唆するように、一般的です不必要なオーバーヘッドを低減コンテキストスイッチ。二つのスレッド、各時間の最初の呼び出しで、本明細書JMH使用されるように、私はマシン上でテストされ、次のように、コードは次のとおりです。
@BenchmarkMode({Mode.SampleTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations=3, time = 5, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations=1,batchSize = 100000000)
@Threads(2)
@Fork(1)
@State(Scope.Benchmark)
public class Myclass {
Lock lock = new ReentrantLock();
long i = 0;
AtomicLong atomicLong = new AtomicLong(0);
@Benchmark
public void measureLock() {
lock.lock();
i++;
lock.unlock();
}
@Benchmark
public void measureCAS() {
atomicLong.incrementAndGet();
}
@Benchmark
public void measureNoLock() { i++; } } 复制代码
以下のような結果アウトテストは以下のとおりです。
テストプロジェクト | テスト結果 |
---|---|
ロック | 26000ms |
CAS | 4840ms |
いいえロックません | 197ms |
ロックは見ることができます5桁で、CASは小さいが、何のロック3桁の数字ではありません、4桁の番号です。このことから、我々はロック> CAS>ロックなしを知ることができます。
私たちのかく乱は、CASは、彼がパフォーマンスを向上させること、ロックの競合を削減する、キュー内のいくつかのインデックス・セットを使用し、CASで使用されています。
JDKロックキューに加えて、CASの他の使用されていない、それは原子ベースCASを使用することです。
3.1.2偽の共有
私は、コンピュータのCPUのキャッシュは、キャッシュサイズはCPU、キャッシュの重要な指標であると言うと、非常に大きなCPU速度の構造や大きさに影響を与えなければならないだろうフォルス・シェアリングの話、CPUの動作周波数の内部キャッシュが非常に高く、一般的にプロセッサであり、同一周波数動作では、効率は、システムメモリとハードディスクよりもはるかに大きいです。実際の仕事、多くの場合、CPUを繰り返す必要があり、同じデータ・ブロックを読み込み、キャッシュサイズを大きくし、内部CPUが大幅にシステムのパフォーマンスを向上させるためには、メモリやハードディスクを探している代わりに、その後、ヒットを読み取ったデータの速度を高めることができます。しかし、CPUのチップ面積やコスト要因から考えられ、キャッシュは非常に小さいです。
CPUのキャッシュはL2キャッシュは、キャッシュに分けることができ、そして今3つの主流のCPUのキャッシュがあり、さらにいくつかの4つのCPUのキャッシュがあります。各キャッシュに格納されたすべてのデータは次の部分のキャッシュであり、製造コスト及びこれら三つの技術的な難しさは、比較的キャッシュを減少し、それは、比較的にその容量を増加しています。
なぜCPU L1、L2、L3はこれを設計キャッシュしていますか?主に高速なプロセッサで、メモリからデータを読み取るには、理由はメモリの速度の(遅すぎる、それ自体では十分ではありませんし、他のそれはあまりにも遠くCPUからですので、数CPUを待つ全体の必要性10またはクロックサイクルの数百も)、CPU速度を確保するために、この時間は、我々は助けに小さく、より高速なメモリを遅らせる必要があり、これはキャッシュです。これは、削除したコンピュータのCPUに興味がある、それを自分でプレイしてもよいです。
インテルは、このようなi7-7700k、8700kなどの新しいCPU、解放を聞くたびに、これらの会議は、CPUのキャッシュサイズ、自己の利益缶サーチダウンのために最適化されたか、記事を公開します。
QConpresentationマーティンとマイクの話はいくつかの時間に各キャッシュを与えられました:
CPUからの | これは、CPUサイクル程度かかり | それは時間程度かかり |
---|---|---|
主記憶 | 60〜80ナノ秒について | |
QPIバス転送(ソケットとの間の、描かれていません) | 20nsの概要 | |
L3キャッシュ | 40~45サイクルについて | 15NSについて |
L2キャッシュ | 10サイクルについて | 3nsの概要 |
L1キャッシュ | 3-4サイクルについて | 1nsの概要 |
登録 | 1サイクル |
キャッシュライン
マルチレベルキャッシュのCPUの中にではなく、保存する別個のアイテムが、保存するキャッシュラインへの戦略の同様の一種pageCaheとして、キャッシュラインのサイズは、典型的には、Javaのロングで64バイトであります8バイト、あなたは8ロングを保存することができますので、たとえば、あなたが長い時間の変数にアクセスし、彼は7をリロード役立つだろう、私たちは、なぜこのような理由のために、あるリンクリストの配列を、選択しないで上記と述べました私たちは、アレイ内の高速アクセスバッファの行を取得するために頼ることができます。
キャッシュラインは、すべての権利ですか?NO、彼はまだ欠点をもたらすので、私はこの欠点の例を与えるためにここにいる、次のように自分のデータを構造化されたキューの配列、ArrayQueueがあることが考えられます。
class ArrayQueue{
long maxSize;
long currentIndex;
}
复制代码
maxSizeのを訪れる際maxSizeのはcurrentIndexのために、配列のサイズを定義している私たちを開始するために、キューの中の当社の現在の位置の印である、この変化が速くなり、あなたが想像することができ、また負荷は、この時間に来たcurrentIndexではありませんこの時点で、彼はまだメモリから続けなければならないアクセスmaxSizeのに続けば、他のスレッドは、currentIndexを更新し、CPUのキャッシュラインが無効で設定しますが、この規定は、CPUであることをしてくださいノートでは、彼は、だけでなく、右のcurrentIndexセット無効です読んで、それは私たちのMaxSize定義され始めている、我々はキャッシュが可能訪れる必要がありますが、影響は我々の絶えず変化currentIndexました。
パディングマジック
パディング方法のかく乱を使用して、上記のキャッシュラインが発生に対処するために、
class LhsPadding
{
protected long p1, p2, p3, p4, p5, p6, p7;
}
class Value extends LhsPadding
{
protected volatile long value;
}
class RhsPadding extends Value
{
protected long p9, p10, p11, p12, p13, p14, p15;
}
复制代码
値がいっぱいに長い役に立たない他の変数の数の一つでした。あなたが値を変更するときに、それは他の変数のキャッシュラインには影響しません。
最後に、ところで、あなたはそれがJVMの設定パラメータ-RestricContentended = FASEを持っているでしょう使用する場合、一般的に、唯一のJDK内部を許可する、もちろん、jdk8に@Contended注釈のために、制限がこの注釈をキャンセルするように設定されています。多くの論文は、のConcurrentHashMapを分析しますが、この注釈の使用上のConcurrentHashMapで無視され、このコメントを、考えるためには、そう、ConcurrentHashMapの各バケットで計算しない別のカウンタであり、これが原因カウンタにすべての時間を変更しますキャッシュラインフィルは、パフォーマンスを向上させるためには、この注釈を使用して最適化されています。
3.1.3RingBuffer
配列かく乱私たちのデータを保存する方法を使用して、我々はまた、キャッシュにアクセスする際に上記のアレイの使用は、私たちに良い使用を保存した導入が、さらにリングバッファである円形配列かく乱、に格納されたデータを使用することを選択しました。ここで、実際に円形アレイの環状アレイを訪問の残りを取るリングバッファのように使用されていない説明するために、そのようなアレイのサイズとして実際に等インデックス0、10、20で10,0訪問この位置の配列は、であり、添字は、この位置0のアクセスのアレイです。
実際には、これらのフレームワークは残りは%演算子を使用していない、およびサイズを設定する必要があり操作&が、通常マイナスので、等、10は2のn乗、100を、使用される1取ります単語は、インデックス・(サイズ-1)をうまく利用することができ、1,11,111ので、使用が増加するにアクセス速度を計算するビット。あなたはサイズ設定のための2のかく乱Nの力を持っていない場合、彼はBUFFERSIZEが2つのN異常の力でなければなりませんがスローされます。
もちろん、それは迅速なアクセスの配列の問題を解決するだけでなく、ために私たち0,10,20と、ガベージコレクションを減らし、再びメモリを割り当てる必要の問題を解決する、などだけでなく、あなたが再割り当てする必要はありませんので、メモリの面積を行っていますメモリは、頻繁にJVMのごみ収集です。
それ以来、財団のためのかく乱などの高性能パッドへのこれら三人の大殺害デバイスで、すでに終了3を殺します。次はでかく乱かく乱し、特定の作品を使用する方法について説明します。
使用方法3.2Disruptor
以下は簡単な例を与えます:
ublic static void main(String[] args) throws Exception {
// 队列中的元素
class Element {
@Contended
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
// 生产者的线程工厂
ThreadFactory threadFactory = new ThreadFactory() { int i = 0; @Override public Thread newThread(Runnable r) { return new Thread(r, "simpleThread" + String.valueOf(i++)); } }; // RingBuffer生产工厂,初始化RingBuffer的时候使用 EventFactory<Element> factory = new EventFactory<Element>() { @Override public Element newInstance() { return new Element(); } }; // 处理Event的handler EventHandler<Element> handler = new EventHandler<Element>() { @Override public void onEvent(Element element, long sequence, boolean endOfBatch) throws InterruptedException { System.out.println("Element: " + Thread.currentThread().getName() + ": " + element.getValue() + ": " + sequence); // Thread.sleep(10000000); } }; // 阻塞策略 BlockingWaitStrategy strategy = new BlockingWaitStrategy(); // 指定RingBuffer的大小 int bufferSize = 8; // 创建disruptor,采用单生产者模式 Disruptor<Element> disruptor = new Disruptor(factory, bufferSize, threadFactory, ProducerType.SINGLE, strategy); // 设置EventHandler disruptor.handleEventsWith(handler); // 启动disruptor的线程 disruptor.start(); for (int i = 0; i < 10; i++) { disruptor.publishEvent((element, sequence) -> { System.out.println("之前的数据" + element.getValue() + "当前的sequence" + sequence); element.setValue("我是第" + sequence + "个"); }); } } 复制代码
ThreadFactory:さらにいくつかの重要なかく乱であり、これは、スレッド、我々消費者ニーズのかく乱の生産のための時間でスレッドファクトリです。EventFactoryは:イベントの植物は、工場がかく乱で私たちのキュー要素を生成するために使用される、彼は代わりに一度、初期化時にリングバッファがいっぱいに指示します。イベントハンドラ:EventHandlerは、消費者として見ることができるイベントを、処理するためのハンドラが、彼らは独立したコンシューマ・キューです以上のEventHandler。WorkHandler:ハンドラがイベントを処理するために使用され、そして上記の違いは、複数の消費者が同じキューを共有していることです。WaitStrategy:待機戦略は、ポリシーデータは何もしない場合かく乱は、消費者は、時期を決定するために、消費者を獲得したいくつかの戦略があるのですか?戦略かく乱のどの部分の簡単なリストを以下に示します
-
BlockingWaitStrategy:スレッドが途中で遮断されて、プロデューサーのための待ち時間は、目を覚ます消費者かどうかに依存するシーケンスチェックをリサイクルした後に目を覚まします。
-
BusySpinWaitStrategy:スレッドが待っているスピン、より多くのCPU消費することがあり
-
LiteBlockingWaitStrategyは:2つのスレッドがアクセスWAITFOR、訪問signalAllにアクセスする場合は、スレッドがBlockingWaitStrategy、signalNeeded.getAndSetの違いと比較して、プロデューサーのきっかけを待ってブロックされ、ロックされたロックの数を減らすことができます。
-
LiteTimeoutBlockingWaitStrategyは:LiteBlockingWaitStrategyと比較すると、例外をスローした後、より多くの時間よりも、ブロックする時間を設定します。
-
YieldingWaitStrategy:Thread.yield(その後、100回を試してみてください)ので、CPUいます
EventTranslator:かく乱イベントに循環している当社の他のデータ構造を変換することができ、このインタフェースを実装します。
ワークス3.3
これは、偽の共有を減らす、CAS上で説明されてきた、リングバッファ3は、かく乱の生産者と消費者の導入のためのプロセス全体を見て殺しました。
3.3.1生産
生産のために、それはProducerType.Single単独複数の生産者と生産者、に分けることができ、シングルスレッドであるため、単一の生産のでProducerType.MULTIは、複数の生産者及び単一CASの複数のプロデューサを区別し、スレッドの安全性を確保する必要はありません。
破砕に一般的)(単一の質量とdisruptor.publishEventとdisruptor.publishEventsで行われます。
キューにイベントかく乱を公開、以下の手順が必要です。
- まず、リングバッファはリングバッファに掲載される次の位置を取得し、これは、2つのカテゴリに分けることができます。
- 位置を書かれたことはありません
- すべての消費者は、あなたが場所を書くことができ、読んでてきました。あなたは常に読んしようと読んでいない場合は、かく乱は非常に賢い行うとCPUを占有する必要はありませんでしたが、LockSuport.park()は、それがされることにより、CPUを防止するために、スレッドは行われていた、保留中のアクションをブロックされていますこの空気循環、または他のスレッドがグラブCPUのタイムスライスではありません。CASはあなたがいない場合、必要にシングルスレッドで、位置をつかむ取得した後に行われます。
- 我々はイベントで説明した第1のステップリングバッファ上にEventTranslatorを呼び出す横EventTranslatorの位置が書き換えられること。
- パブリッシュ、avliableBuffer 10が対応する位置にレコードに書き込んだときに、最新のシリアル番号は、どのくらい例えばそのテイク0,10,20上にある現在の位置リングバッファを記録するために使用される追加の配列撹乱があるに10が現在属している、使用しているもの、それは後に導入されます。このavliableBufferを更新する必要を公開し、ときにすべての生産をブロック目を覚まします。
プロセスを塗装で簡単に見ているの後、我々は、上記の例10を取る、我々はBUFFERSIZE = 8例を取るためにここにいるので、bufferSizeのNは、2の累乗でなければならないので、間違っている:イベントは8の周りにあるとき、ここでは、プッシュを持っていますプロセスのいくつかは、続いて、次に3つのメッセージプッシュ場合:(3)次に1.最初の呼び出しを次の3つは9、8であるので、我々は7でこのポジションであり、残りを取るある0,1,2 。これら3つのメモリ領域2のなし0,1,2書き換え。3.書き込みavaliableBuffer。
はい、私は2フェーズ・コミット、そのために私たちの2PCに類似している、上記のプロセスがそれに精通していないことを知っている、第一の位置リングバッファロックして、消費者に知らせるためにコミットしていません。2PCの特定のプレゼンテーションは私の他の記事を参照することができ、その後誰かが彼にこの記事を表示するには、あなたに分散トランザクションを要求します。
3.3.1消費者
消費者のために、上記の説明では、2分割され、一つはキューと、より複雑な個人消費についてはこちらより導入された複数の独立した消費者支出、1が同じキューである以上支出の消費者であり、この独立性を理解することができ、消費者を理解することができるようになります。私たちのdisruptor.stratでは()メソッドは、バックグラウンドスレッドの消費を行うために、私たち消費者を開始します。私たちの注意を必要とする消費者の間で2つのキューがありますが、一つはすべての消費者がキューの進捗状況を共有し、それぞれが独立した個人消費のスケジュールキューです。1.消費者は次の次は、CASをつかむためだけでなく、自分自身の消費マークのキューの現在の進捗状況の進展を待ち行列を共有しました。2.次のように、アプリケーションがすぐ隣には適用されない独自の位置リングバッファ可読次に、に適用するためには、次によりも大きい範囲に適用する可能性があり、ブロッキング戦略を適用するプロセスです。
- プロデューサーリングバッファは、最新の書き込み位置を取得します
- それは私が読書の位置を申請したいと思い未満であるかどうかを確認
- この位置は、プルーフよりも大きい場合には、生産者へのリターンを書かれています。
- 少ない証拠よりも、この位置に書かれていない場合は、ブロックする戦略がブロックされます、それは覚醒段階のプロデューサーに提出されます。3.可読チェックこの位置、消費者は位置8と内部書かれたこのシリアル番号の10にしている場合、アプリケーションの位置は、次に読み出す適用、例えば7で現在プロデューサとして、連続的であってもよいので、あなたが位置8まで縮小しなければならなかったので、最初のステップ10は、返却はなく、9実際に読み込まれますので、この位置を書くために9ではなく、十分な時間。4.次の電流よりも小さくなった後収縮が終わった場合、ループは適用し続けています。handler.onEvent()の処理に5
この例のように、我々は8 =次のこのポジションに適用したいです。共有進捗8をつかむためにキュー1.まず、独立したコホートで書かれた進歩7 2.生産者は8,9,10を生産しているため異なる戦略によると、私たちは、ブロックすることを選択した最大位置読める8を、取得、戻り、そう従って、再度比較avaliableBufferする必要はありません10はそう。最後に処理するためのハンドラに3。
4.Log4jかく乱
同期のスループット次の図に示すかく乱使用とArrayBlockingQueueと比較Log4jに、Log4jのはもちろん、ここで紹介する多くのフレームかく乱の使用は、ない、他のかく乱完全な爆発を使用して見ることができます。
遂に
この記事では、従来のブロッキングキューの欠点を説明し、後でかく乱の下で力を吹いに焦点を当て、そしてなぜ彼は非常に高速なハードウェア、ならびに特定のワークフローです。
誰かが、効率的なロックフリーキューを設計するためにあなたを尋ねた後、あなたが呼び出す場合、あなたはどのように設計する必要がありますか?私はそれが疑問を持っているか、アイデアを交換するために、私は私の公共の数に焦点を当てることができ、議論するために私の友人と私を追加希望の場合は、記事から答えをまとめると信じています。
:私の最後の記事は、一緒に一緒に構築することができ、あなたは、オープンソース・プロジェクトの維持に参加したい場合は、Javaは、もちろん学習構築するために、コミュニティ、githubのアドレスで、優れたJGrowing、包括的に含まれたgithub.com/javagrowing ...トラブル小さな星よと。
著者:カフェラテの
リンクします。https://juejin.im/post/5b5f10d65188251ad06b78e3
出典:ナゲッツの
著者によって予約の著作権。著者は認可商業転載してください接触、非商用の転載は、ソースを明記してください。