ネッティーなぜちょうどAtomicXXXを使用するのではなく、変数を更新するためにAtomicXXXFieldUpdaterを使用していませんか?

私はより多くの技術の共有に焦点を当てることができます

序文

あなたは慎重にスレッドプールを作成するために、ネッティー、またはNIOスレッドオブジェクトとソースコードをソースコードのスレッドスケジューリングモデルを読み込む場合は、確かに普通の原子パッキングを使用しない理由と同様の「AtomicIntegerFieldUpdater」フィギュア、--Nettyない助けしかし不思議を体験変数を数えるような?

以下、この質問では、綿密ネッティーおよびJDKのソースコードは、高度な使用法を学ぶための方法をか2をスパイします。オリジナル:ネッティーなぜちょうどAtomicXXXを使用するのではなく、変数を更新するためにAtomicXXXFieldUpdaterを使用していませんか?

JDKのクラスが実装機構の操作アトミックアトミック

以下のようにJDKでは、クラスの先頭にアトミック操作がアトミック多くの一般的なデジタル関与するJavaの型があり、基本的な操作は、対応するクラスアトミック原子を持っています:

原子クラスは、コーディングするときに安全に使用することができ、スレッドセーフ操作です。一例として、一般的に使用されるクラスでのAtomicInteger原子以下では、これらのクラス原子を実現するための根本的なメカニズム、なぜノー直接ネッティー原子クラスを理解するための援助の分析。ない書き込みに使用される特定のデモは、おそらくJavaerが使用されているか、どのように多くは、これまでのAtomicIntegerコアソースを直接見、見ました:

1つの プライベート 揮発性 のint値; //は、いくつかの非コアソース簡素化
2  3。// 初期化、いくつかの非コアソースが簡略化される4。公共のAtomicIntegerの(int型はinitialValue)を{
 5。      値= はinitialValue;
 6 }
 7。公的最終int型のGET(){
 8 戻り値;
 9 }
 10 // インクリメント、および自己増力の以前の値を返す     11。公的最終INT getAndIncrement(){
 12はリターン unsafe.getAndAddInt(この 
            
        、ValueOffset、1 );
 13である }
 14  // デクリメント1、インクリメント前の値を返す     
15  公的 最終 INT getAndDecrement(){
 16      リターン unsafe.getAndAddInt(この、valueOffset、-1 );
 17 }

上記、のAtomicIntegerは増減操作の安全なint型の値を通すかもしれません。あなたはそれがJavaの実装ではありませんので、あなたは、近い機能の下に多くのことを達成することができ、これは魔法のJDKクラスで、操作の基本的なスレッドセーフなメソッドは、スレッドセーフでないメソッドを使用して実装され、ソースコードから見ることができますが、これらは基礎となることを確実にすることができます安全でないアプリケーションパース(https://tech.meituan.com/2019/02/14/talk-about-java:getAndXXX操作には、この資料の魔法のJavaクラスを参照することができ、特定の使用についてと危険な詳細を、スレッドセーフです-magicクラス-unsafe.htmlは、直接開くことはできません、あなたのブラウザにペーストをコピーすることができます)

余談:もしAtomicXXXオブジェクトは、カスタムタイプですか?パニック、Javaは、カスタムタイプアトミック操作クラス--AtomicReferenceを提供し、それは、操作の対象となる一般的なオブジェクトであり、それに応じて、操作の方法を底インクリメント方法ではないカスタム・タイプをサポートすることができパラメータ渡し、次のソースコード:

1  // X操作のためには、accumulatorFunction行わ
 2  // accumulatorFunctionが関数で、カスタムやりたいことが
 3  //は古い値を返します
。4  パブリック 決勝V getAndAccumulate(Vは、xは
 5                                  BinaryOperator <V> accumulatorFunction){
 。6      // PREVがあります古い値は、次の新しい値である
7。     V PREV、次の;
 。8      // スピン+ CASの保証は古い値を置き換えることができます
9。     やる{
 10          PREV = GET();
 11          // カスタムアクションを実行する
12は          次= accumulatorFunction.apply( PREV、X);
 13であります     } ながら(!のcompareAndSet(PREV、NEXT))。
14      戻り前。
15 }

 

JDKのAtomicXXXFieldUpdaterアトミック更新とその利点

Java5では、JDKクラスを提供原子を始め、もちろん、更新原子を含む - すなわちクラスFieldUpdaterサフィックスを、整数、限り、カスタム型アトミック更新、合計3つのタイプがあります。

これらの優れたオープンソースのフレームワークの様々な共通アップデータの原子が、ほとんど使用しない直接一般的なビジネスプログラマは、実際には、これらの原子はまたに(揮発性は、オブジェクトのプロパティを変更する必要があります)パッケージ共有変数を更新するために使用することができます原子は、これらの共有変数に更新された機能を実現します。これらのパッケージ化されている共有変数も参照型であってもよい、プリミティブすることができ、その後、頼む:原子クラスはまた、原子の追加のセットを提供している、なぜそれが更新されますか? 

簡単に言えば、int型の変数には2つの理由は、単純に前者が唯一のグローバル静的変数は揮発性AtomicIntegerFieldUpdaterをパッケージ化することができる必要があるためのAtomicIntegerは、int型の変数を詰め直接支出よりも小さく、例えば、原子カウンターに基づいてAtomicIntegerFieldUpdaterを達成、あります非静的変数を共有修飾、および同じクラスの後続の各オブジェクトのみ更新オブジェクトカウンターこの静的アトミック更新を共有する必要があるように、CAS更新原子で達成され、そうすることができる原子を実装し、原子同じクラスの各オブジェクトのクラスは、カウンタ+のAtomicIntegerオブジェクトを作成して、このオーバーヘッドは明らかに比較的大きいです。

JDK更新原子、即ち、JDK BufferedInputStreamを以下の例を参照して、以下に抜粋源です。

1  パブリック クラスにBufferedInputStreamは延びFilterInputStream {
 2      プライベート 静的 INT = 8192 DEFAULT_BUFFER_SIZEを3      プライベート 静的 int型 MAX_BUFFER_SIZE = Integer.MAX_VALUEの- 8 4      保護された 揮発性 バイトBUF []。
5      / ** 
6       *アトミックアップデータはBUFのためのcompareAndSetを提供します。これは、
 7       が閉じを非同期にすることができるため*必要。私たちは、nullであるかどうか使用
 8       このストリームが閉じていることを主な指標としてBUF []の*を。9      * "の"フィールドも近い上にゼロにされている。)
 10       * / 
11      プライベート 静的 最終
12          AtomicReferenceFieldUpdater <BufferedInputStreamを、バイト []> bufUpdater =
 13          AtomicReferenceFieldUpdater.newUpdater
 14          (BufferedInputStreamを。クラスバイト []。クラス、 "BUF" );

図から分かるように、各オブジェクトは、オブジェクトのプロパティであるにBufferedInputStreamのBUFプロパティを含み、意志が変更され、原子アップデータAtomicReferenceFieldUpdater包装、参照型は、静的型、手段のアトミック更新があることに注意してくださいグローバルアップデートで唯一の原子であり、ユーザーが作成したオブジェクトは、作成されたどのように多くのBufferedInputStreamを関係なく、なぜ直接bufのプロパティをパッケージAtomicReferenceここでは原子ではないクラス、bufがバイト配列、通常は比較的大きなオブジェクトであるためあなたが直接、原子クラスのパッケージを使用する場合は、後続の各BufferedInputStreamを追加オブジェクトは、クラス原子のオブジェクトを作成し、それがソースコード内でより多くのメモリ、重い負担、代わりにクラスのアトミックアトミック更新のため、JDK直接使用、ネッティーを消費します同様の使用はまったく同じです。

もう一つの重要な理由は、原子アップデータの使用は、JDKの例に戻ると、BUFの外はまだ保持できるオブジェクトbufは、元の配列プロパティを共有変数の元の構造を破壊しませんが、より揮発性の修正よりも、外の世界が直接できることですこのバイト配列は遅延しない2として記述することができ、いくつかのビジネスロジックを実装するために、必要なときにも、原子アップデータアトミックな更新を使用して達成することができ、柔軟性に強いです!

そこものの、原子アップデータが静的であることを理解することは疑いの必要性のポイントであるが、それは共有変数は、まだ実際にはまだ一つだけのシェアを所有して含まれている各クラスのオブジェクトされているオブジェクトのプロパティのクラスで変更可能アトミックアップデータが静的であるため、変数は、どのような方法で影響を受けません。

結論:アトミック更新を達成するための最良の方法は、直接原子更新を使用して実装されます。一方では、使用、より柔軟な、一方で元の共有変数を破壊しない、メモリを節約することが多いです。もちろん、場合遅延要件が非常に高い場面で、あなたはすべての原子シンプルな高効率のコーディング、開発の種類、あまりエラーが発生しやすいの後、OKの原子クラスのように過酷な、直接使用する必要はありませんされていません。

ネッティー製品のソースコードは、アトミック更新の最適な実装を学びます

私は、以前に述べた理論の多くは、ソースコードネッティー、網状の以下のセクションでは、どのようにエレガントな原子アップデータの使用を参照参照してください。以下は、ネッティーNIOスレッドの実装クラスである--SingleThreadEventExecutorソース部分が省略され、この分析のコードの独立の多く:

1  / ** 
2つの {ため*抽象基底クラス@link 単一のスレッドに、すべての送信タスクを実行OrderedEventExecutor}さん。
3   * / 
4  パブリック 抽象 クラス SingleThreadEventExecutorは延び AbstractScheduledEventExecutorの実装OrderedEventExecutor {
 5      プライベート 静的 最終 int型 ST_NOT_STARTED = 1 6      プライベート 静的 最終 int型 ST_STARTED = 2 7      プライベート 静的 最終 int型の ST_SHUTTING_DOWN = 3;
8      プライベート 静的 最終 int型 ST_SHUTDOWN = 4 9      プライベート 静的 最終 int型 ST_TERMINATED = 5 ;
10  11 プライベート静的最終 AtomicIntegerFieldUpdater <SingleThreadEventExecutor> STATE_UPDATER。
12 プライベート静的最終 AtomicReferenceFieldUpdater <SingleThreadEventExecutor、ThreadProperties> PROPERTIES_UPDATER。
13 プライベート静的最終長い SCHEDULE_PURGE_INTERVAL = TimeUnit.SECONDS.toNanos(1 )。
14                       15      静的{
 16          AtomicIntegerFieldUpdater <SingleThreadEventExecutor>アップデータ=
 17                  PlatformDependent.newAtomicIntegerFieldUpdater(SingleThreadEventExecutor。クラス "状態" )。
18          であれば(アップデータ== NULL ){
 19              アップデータ= AtomicIntegerFieldUpdater.newUpdater(SingleThreadEventExecutor。クラス "状態" )。
20          }
 21          STATE_UPDATER = アップデータ。
22      }
 23  24 民間最終キュー<Runnableを>      タスクキュー;
25      民間 最終エグゼキュータ。
26      プライベート 揮発性のスレッドのスレッド。
27      プライベート 揮発 int型の状態= ST_NOT_STARTED。

小断片上に採取し、ノートを削除し、はっきり見ることができるスレッドオブジェクトは、網状JDK静的定数スレッド状態識別番号、実行のスレッド、タスクキュー非同期、および識別に状態のようなスレッド状態の特性をカプセル化状態に焦点を当てており、このプロパティは、共通の共有揮発性によって修飾変数、静的原子アップデータSTATE_UPDATER包装されています。

下面看NIO线程的启动源码:

1      / ** 
2       スレッド開始の* NioEventLoop方法は、本NIOスレッドが開始されたか否かが決定されるであろう
 。3       * / 
4      プライベート ボイドStartThread(){
 5          IF(STATE_UPDATER.get()== ST_NOT_STARTED){
 6              IF(STATE_UPDATER.compareAndSet (この、ST_NOT_STARTED、ST_STARTED)){
 7。                 doStartThread();
 8              }
 。9          }
 10      }

注释写到了,启动NIO线程之前会做一次是否已经启动的判断,避免重复启动,这个判断逻辑就是前面提到的原子更新器实现的,当本NIO线程实例没有启动时,会做一次CAS计算,注意CAS对应操作系统的一个指令,是原子操作,如果是多个外部线程在启动NIO线程,那么同时只有一个外部线程能启动成功一次,后续的线程不会重复启动这个NIO线程。保证在NIO线程的一次生命周期内,外部线程只能调用一次doStartThread()方法,这样可以实现无锁更新,且没有自旋,性能较好,这里之所以不需要自旋,是因为启动线程就应该是一锤子买卖,启动不成功,就说明是已经启动了,直接跳过,无需重试。

 

スピンで使用状況を表示するには:

NIO糸で閉じたときに(例外であってもよい)エレガントであり、ループを無限であろう、CASアルゴリズムと組み合わせて、アトミック更新は、スレッドの現在の状態にNIOを閉じています。2つの考慮事項があります。

1、および論理開始NIOスレッドのスレッドセーフが同じではありませんが、更新スレッド状態はワンショット契約ではなく、成功しなければならない、あなたは成功したCAS運用まで、スピン再試行する必要があります

2、共有変数の操作の古い値は、CASの間に実行が外部スレッドを変更されないことを確実にするために、共有変数の古い値外部キャッシュローカル変数を使用する必要が

3.同様に、各操作の前にCASの実装は、あなたが唯一の条件に合わせて更新され、古いもの、の値を決定する必要があり、それは本当にCAS操作を実行する、または命令は成功し、パフォーマンスを向上させるために、操作を繰り返す必要が雄ねじを更新されていません。

 

ネッティーはそうすることも、間接的にNertyソースを反映した類似のシーンがある場合、あなたは勉強に、使用これらの2つのタイプを参照することができ、通常、事業開発、確かに非常に良いです。

 

アトミックアップデータの使用の注意事項の概要:

1、パッケージには、揮発性の共有変数を変更する必要があります

2、包装は非静的共有変数でなければなりません

3、ルーチンは、CASで実装し、独自の論理交換を比較しなければなりません

注ロジック4は、自己充実比較およびスワップ:非ワンショット契約アトミック更新操作と、ローカル変数と外部共有変数の古い値をキャッシュする必要があり、参照することができ、特定の理由:網状スレッドスケジューリングモデル分析(10 ) 「マルチスレッド環境、ローカル変数にインスタンス変数は、スキルをプログラミング」、およびその最後の一貫性を確保するためにループ動作を入れました。

 

追伸

dashuaiのブログは、いくつかのPDF電子書籍、情報、ヘルププッシュ内部を共有するために来て、含め、毎日Tucao生涯学習の実践者、メーカーのプログラマ、および仕事の経験に焦点を当て、研究ノートと共有しているが、インターネット業界、これらに限定されない、歓迎Paizhuan!

 

おすすめ

転載: www.cnblogs.com/kubixuesheng/p/12650686.html