1。安全でないクラスソースコード分析
JDKのRt.Jarパッケージの安全でないクラスは、ハードウェアレベルのアトミック操作を提供します。 Unsafeの方法はすべてネイティブの方法であり、JNIを使用してローカルC ++実装ライブラリにアクセスされます。
Rt.jarの安全でないクラスの主な機能の説明。安全でないクラスは、ハードウェアレベルのアトミック操作を提供し、メモリ変数を直接安全に動作させることができます。 JUCソースコードで広く使用されています。その原則を理解することは、JUCソースコードを研究するための基盤となります。
まず、次のように、安全でないクラスでの主要な方法の使用を理解しましょう。
1.Long ObjectFieldOffset(フィールドフィールド)メソッド:属するクラスの指定された変数のメモリオフセットアドレスを返します。オフセットアドレスは、安全でない関数の指定されたフィールドにアクセスするときにのみ使用されます。次のコードは、AtomiclongオブジェクトのAtomiclongの変数値のメモリオフセットを取得するために安全ではありません。コードは次のとおりです。
static {try {valueoffset = unsafe.objectfieldoffset(atomiclong.class.getDeclaredfield( "value")); } catch(Exception ex){throw new error(ex); }}2.Int ArrayBaseOffset(クラスArrayClass)メソッド:アレイの最初の要素のアドレスを取得します
3.int arrayindexscale(class arrayclass)メソッド:配列内の単一の要素で占めるバイト数を取得します
3.Boolean CompareAndSwaplong(オブジェクトOBJ、長いオフセット、長い予想、長い更新)方法:オブジェクトOBJのオフセットオフセットの変数の値が予想と等しいかどうかを比較します。等しい場合、更新値で更新され、trueを返します。
4.パブリックネイティブの長いGetLongVolative(オブジェクトOBJ、ロングオフセット)メソッド:オブジェクトOBJのオフセットオフセットの変数に対応する揮発性メモリセマンティクスの値を取得します。
5.Void PutOrderEdLong(オブジェクトOBJ、ロングオフセット、長い値)メソッド:OBJオブジェクトのオフセットオフセットアドレスに対応する長いフィールドの値を値に設定します。これは、遅延を伴うputlongvolatileメソッドであり、値の変更が他のスレッドにすぐに表示されることを保証するものではありません。変数は、揮発性で変更され、予期せず変更されると予想される場合にのみ役立ちます。
6.Void Park(Boolean Isabsolute、Long Time)方法:現在のスレッドをブロックします。パラメーターのイソアブソリュートがfalseに等しい場合、0に等しい時間は常にブロックすることを意味します。 0を超える時間は、指定された時間を待った後、ブロッキングスレッドが目覚めることを意味します。今回は相対値、つまり、現在のスレッドが現在の時間に比べて時間を蓄積した後に目覚めます。イスバソルートが真と等しく、時間が0より大きい場合、指定された時点にブロックした後に目が覚めることを意味します。ここでは、時間は絶対的な時間であり、特定の時点でMSに変換された値です。さらに、他のスレッドが現在のブロッキングスレッドの割り込みメソッドを呼び出して現在のスレッドを割り込むと、現在のスレッドも戻ります。他のスレッドがunparkメソッドを呼び出して現在のスレッドをパラメーターとして使用すると、現在のスレッドも戻ります。
7.Void Proark(オブジェクトスレッド)メソッド:パークを呼び出した後、ブロッキングスレッドを起動します。パラメーターは、目が覚める必要があるスレッドです。
JDK1.8にいくつかの新しい方法が追加されました。次のように、長いタイプを操作する方法の簡単なリストを次に示します。
8. LONG GETANDSETLONG(オブジェクトOBJ、ロングオフセット、ロングアップデート)メソッド:オブジェクトOBJでオフセットを使用して可変揮発性セマンティクスの値を取得し、変数揮発性セマンティクスの値を更新して設定します。使用方法は次のとおりです。
パブリックファイナルロングgetandsetlong(オブジェクトobj、long offset、long update){long l; do {l = getLongVolatile(obj、offset); //(1)} while(!compareandswaplong(obj、offset、l、update)); lを返します。 }内部コード(1)から、getLongVolativeを使用して現在の変数の値を取得し、CAS原子操作を使用して新しい値を設定できます。ここで、While Loopsを使用すると、複数のスレッドが同時に呼び出す状況を考慮し、CASが故障した後にスピン再生が必要です。
9.Long getandaddlong(オブジェクトOBJ、ロングオフセット、ロングバリュー)メソッド:オブジェクトOBJのオフセットで変数揮発性のセマンティクスの値を取得し、変数値を元の値 + addValueに設定します。使用方法は次のとおりです。
パブリックファイナルロングgetandaddlong(オブジェクトobj、long offset、long addvalue){long l; {l = getLongVolatile(OBJ、Offset); } while(!compareandswaplong(obj、offset、l、l + addvalue)); lを返します。 }getandsetlongの実装と同様ですが、ここでCASを使用する場合、元の値 +転送された増分パラメーターaddvalueの値が使用されます。
では、安全でないクラスを使用する方法は?
危険なのはとても素晴らしいのを見て、本当に練習したいですか?さて、まず次のコードを見てみましょう。
パッケージcom.hjc; Import sun.misc.unsafe;/*** 2018/6/6にCONGによって作成されました。 */public class testunsafe {// Unsafe(2.2.1)Static Final Unsafe Unsafe = unsafe.getunsafe()のインスタンスを取得します; //クラスTestunsafe(2.2.2)の静的最終的な長いステートオフセットの変数状態のオフセット値を記録します。 //変数(2.2.3)私的揮発性長い状態= 0; static {try {//クラスTestunsafe(2.2.4)stateoffset = unsafe.objectfieldoffset(testunsafe.class.getDeclaredfield( "state"))で状態変数のオフセット値を取得します。 } catch(Exception ex){system.out.println(ex.getLocalizedMessage());新しいエラー(ex)をスロー; }} public static void main(string [] args){//インスタンスを作成し、状態値を1(2.2.5)testunsafe test = new testunsafe()に設定します。 //(2.2.6)boolean sucess = unsafe.compareandswapint(test、stateoffset、0、1); System.out.println(sucess); }}Code(2.2.1)Unsafeのインスタンスを取得し、コード(2.2.3)は0に初期化された変数状態を作成します。
コード(2.2.4)は、unsafe.objectfieldoffsetを使用して、testunsafeオブジェクトのtestunsafeクラスの状態変数のメモリオフセットアドレスを取得し、それをStateOffset変数に保存します。
コード(2.2.6)は、作成されたUnsafeインスタンスのCompareAndSwapintメソッドを呼び出し、テストオブジェクトの状態変数の値を設定します。具体的には、テストオブジェクトのオフセットがStateOffsetが0である状態変数が0の場合、更新値は1に変更されます。
上記のコードにtrueを入力したいのですが、実行後、次の結果が出力されます。
なぜこれが起こっているのですか? GetunSafeコードを入力する必要があります。
Private static Final Unsafe theunsafe = new Unsafe(); public static unsafe getunsafe(){//(2.2.7)class localclass = reflection.getCallerclass(); //(2.2.8)if(!vm.issystemdomainloader(localclass.getClassLoader())){throw new SecurityException( "Unsafe"); } return theunsafe;} // paramclassloaderがブートストラップクラスローダーであるかどうかを判断します(2.2.9)public static boolean issystemdomainloader(classloader paramclassloader){return paramclassoloder == null; }コード(2.2.7)getunsafeを呼び出すオブジェクトのクラスオブジェクトを取得します。ここにtestunsafe.calsがあります。
コード(2.2.8)は、ブートストラップクラスローダーによってロードされたローカルクラスであるかどうかを決定します。ここで重要なのは、ブートストラップローダーがtestunsafe.classをロードするかどうかです。 Java Virtual Machineのクラスロードメカニズムを見た人は、Testunsafe.classがAppClassloaderを使用してロードされているためであるため、ここに例外が直接スローされていることが明らかにわかります。
質問は、なぜこの判断を下す必要があるのですか?
安全でないクラスがRt.Jarで提供されており、Rt.JarのクラスがBootstrapクラスローダーを使用してロードされていることがわかっています。メイン関数を開始するクラスは、AppClassLoaderを使用してロードされるため、親の委任メカニズムがBootStrapに委任して危険なクラスをロードすることを考慮して、メイン関数に安全でないクラスをロードするときにロードされます。
コード(2.2.8)の認証がない場合、アプリケーションは安全でないことを使用して自由に物事を行うことができます。安全でないクラスはメモリを直接動作させることができますが、これは非常に安全ではありません。したがって、JDK開発チームはこの制限を特別に行い、開発者が通常のチャネルで安全でないクラスを使用することはできませんが、Rt.jarのコアクラスで安全でない関数を使用します。
問題は、安全でないクラスを本当にインスタンス化し、安全でない機能を使用したい場合はどうすればよいですか?
反射の黒人技術を忘れてはならず、Unsafeのインスタンス方法を取得するためにユニバーサルリフレクションを使用してください。コードは次のとおりです。
パッケージcom.hjc; import sun.misc.unsafe; Import Java.lang.Reflect.field;/*** 2018/6/6にCONGによって作成されました。 */public class testunsafe {static final unsafe unsafe;静的最終的なロングステートオフセット。民間の揮発性長い状態= 0; static {try {//反射してtheunsafeメンバー変数theunsafe(2.2.10)field field = unsafe.class.getDeclaredfield( "theunsafe"); //アクセス可能(2.2.11)field.setAccessible(true)に設定します。 //この変数の値を取得(2.2.12)unsafe =(unsafe)field.get(null); // testunsafe(2.2.13)の状態のオフセットを取得しますstateoffset = unsafe.objectfieldoffset(testunsafe.class.getDeclaredfield( "state")); } catch(Exception ex){system.out.println(ex.getLocalizedMessage());新しいエラー(ex)をスロー; }} public static void main(string [] args){testunsafe test = new testunsafe(); boolean success = unsafe.compareandswapint(Test、StateOffset、0、1); System.out.println(sucess); }}上記のコード(2.2.10 2.2.11 2.2.12)が安全でない例を反映している場合、実行された結果は次のとおりです。
2。Locksupportクラスのソースコードに関する研究
JDKのRt.JarのLocksupportはツールクラスであり、その主な機能はスレッドを吊り下げて目覚めることです。これは、ロックやその他の同期クラスを作成するための基礎です。
Locksupportクラスは、それを使用する各スレッドに関連付けられます。デフォルトでLocksupportクラスのメソッドを呼び出すスレッドは、ライセンスを保持していません。 Locksupportは、安全でないクラスを使用して内部的に実装されています。
ここでは、次のように、locksupportのいくつかの重要な機能に注意を払う必要があります。
1.Void Park()メソッド:スレッドコールパーク()がlocksupportに関連付けられたライセンスを取得した場合、locksupport.park()を呼び出してすぐに戻ります。それ以外の場合、呼び出しのスレッドは、スレッドのスケジューリングに参加することを禁止されます。つまり、ブロックされ、停止されます。次のコードは次の例です。
パッケージcom.hjc; Import java.util.concurrent.locks.locksupport;/*** 2018/6/6にCONGによって作成されました。 */public class locksupporttest {public static void main(string [] args){system.out.println( "park start!"); locksupport.park(); System.out.println( "Park Stop!"); }}上記のコードに示すように、メイン関数のパークメソッドを直接呼び出すと、最終結果は公園の開始のみを出力します!通話スレッドがデフォルトでライセンスを保持しないため、現在のスレッドは中断されます。操作結果は次のとおりです。
他のスレッドがuparark(スレッドスレッド)メソッドを呼び出すと、現在のスレッドがパラメーターとして使用されると、Parkメソッドを呼び出すスレッドが返されます。さらに、他のスレッドは、ブロッキングスレッドの割り込み()メソッドを呼び出します。割り込みフラグが設定されている場合、またはスレッドの誤った目覚めの後にブロッキングスレッドが戻ってくる場合、ループ条件を使用して判断することをお勧めします。
ブロックされたPark()メソッドを呼び出すスレッドは、他のスレッドによって中断され、ブロックされたスレッドのリターンが中断された例外をスローしないことに注意する必要があります。
2.void upark(thread thread)メソッドスレッドがpararkを呼び出す場合、パラメータースレッドがスレッドとlocksupportクラスに関連付けられたライセンスを保持しない場合、スレッドに保持します。 Park()と呼ばれるスレッドが前に吊り下げられ、スレッドが呼び出された場合、Proarkが呼び出された後にスレッドが目覚めます。
スレッドが以前に公園に電話をかけていなかった場合、unparkメソッドを呼び出した後、Park()メソッドはすぐに返されます。上記のコードは次のように変更されます。
パッケージcom.hjc; Import java.util.concurrent.locks.locksupport;/*** 2018/6/6にCONGによって作成されました。 */public class locksupporttest {public static void main(string [] args){system.out.println( "park start!"); //現在のスレッドにライセンスlocksupport.unpark(thread.currentthread())を取得するようにします。 // Park locksupport.park()を再度呼び出します。 System.out.println( "Park Stop!"); }}操作結果は次のとおりです。
次に、公園の理解を深めるための例を検討しています。
java.util.concurrent.locks.locksupport;/***は、2018/6/6年にCONGによって作成されました。 */public class locksupporttest {public static void main(string [] args)throws strows arturnedexception {thread thread = new runnable(){@override public void run(){system.out.println( "Child Sthrea start!"); }); //チャイルドスレッドスレッドを起動します。start(); //メインスレッドは1Sスレッドをスリープします。スリープ(1000); system.out.println( "メインスレッドunpark start!"); // unparkを呼び出してスレッドにライセンスを保持するようにすると、Parkメソッドはlocksupport.unpark(スレッド)を返します。 }}操作結果は次のとおりです。
上記のコードは、最初にチャイルドスレッドスレッドを作成します。起動後、チャイルドスレッドはパークメソッドを呼び出します。デフォルトの子スレッドはライセンスを保持していないため、それ自体を掛けます。
メインスレッドは1秒間眠ります。目的は、メインスレッドが略式メソッドを呼び出し、チャイルドスレッドがチャイルドスレッドパークを出力できるようにすることです。とブロック。
メインスレッドは、パラメーターがチャイルドスレッドであるため、未熟な方法を実行します。目的は、子スレッドがライセンスを保持し、子スレッドが返すように呼ばれる公園方法です。
公園の方法を返すとき、それがどのような理由が戻っているのかはわかりません。したがって、発信者は、現在の呼び出しでどのパーク方法に基づいて状態が満たされているかどうかを再度確認する必要があります。それが満たされていない場合、彼は再び公園の方法に電話する必要があります。
たとえば、スレッドが返されるときの割り込み状態は、コールの前後に割り込み状態の比較に基づいて中断されるために戻るかどうかを判断できます。
パークメソッドを呼び出した後のスレッドが中断された後に戻ることを示すために、上記の例コードを変更し、locksupport.unpark(スレッド)を削除します。次に、thread.interrupt()を追加します。コードは次のとおりです。
java.util.concurrent.locks.locksupport;/***は、2018/6/6年にCONGによって作成されました。 */public class locksupporttest {public static void main(string [] args)throws arturnedexception {thread thread = new runnable(){@override public void run(){system.out.println( "subthread park start!"); {locksupport.park(); //子スレッドスレッドを起動します。Start(); //メインスレッドは1Sスレッドスリープスリープ(1000)をスリープします。 system.out.println( "メインスレッドunpark start!"); //子スレッドスレッドを割り当てます。Interrupt(); }}操作結果は次のとおりです。
上記のコードと同様に、子スレッドが中断された後にのみ、子スレッドは終了します。子スレッドが中断されていない場合、unpark(スレッド)を呼び出しても、子スレッドは終了しません。
3.Void Parknanos(Long Nanos)メソッド:Parkと同様に、スレッドコールパークがLocksupportに関連付けられたライセンスを取得した場合、Call locksupport.park()はすぐに戻ります。違いは、スレッドを呼び出すスレッドが取得されない場合、それは吊り下げられ、その後ナノス時間の後に戻ることです。
Parkは、ブロッカーパラメーターを備えた3つの方法もサポートしています。スレッドがライセンスを保持せずにパークを呼び出してブロックされ、吊り下げられている場合、ブロッカーオブジェクトはスレッド内に記録されます。
診断ツールを使用して、スレッドがブロックされる理由を観察します。診断ツールは、GetBlocker(Thread)メソッドを使用してブロッカーオブジェクトを取得します。したがって、JDKは、ブロッカーパラメーターを使用してPark Methodを使用し、これにブロッカーを設定することを推奨するため、メモリダンプが問題をトラブルシューティングすると、どのクラスがブロックされているかがわかります。
例は次のとおりです。
java.util.concurrent.locks.locksupport;/***は、2018/6/6年にCONGによって作成されました。 */public class testPark {public void testpark(){locksupport.park(); //(1)} public static void main(string [] args){testpark testpark = new testPark(); testpark.testpark(); }}操作結果は次のとおりです。
ブロッキングを実行していることがわかりますので、JDK/Binディレクトリのツールを使用して見てみる必要があります。わからない場合は、最初にJVM監視ツールを見てみることをお勧めします。
JSTACK PIDを使用して実行後にスレッドスタックを表示する場合、表示できる結果は次のとおりです。
次に、上記のコード(1)を次のように変更します。
locksupport.park(this); //(1)
もう一度実行し、JSTack PIDを使用して結果を次のように表示します。
ブロッカーを使用した公園方法の後、スレッドスタックはオブジェクトのブロックに関するより多くの情報を提供できることがわかります。
次に、公園のソースコード(オブジェクトブロッカー)関数を確認します。ソースコードは次のとおりです。
public static void park(オブジェクトブロッカー){//通話スレッドを取得しますt = thread.currentthread(); //ブロッカー変数SetBlocker(T、Blocker)を設定します。 //スレッドをunsafe.park(false、0l); //スレッドがアクティブになった後にブロッカー変数をクリアします。これは、スレッドがsetblocker(t、null)がブロックされているときに通常分析されるためです。}スレッドクラスの揮発性オブジェクトパークブロッカーに変数があります。これは、公園を通過するブロッカーオブジェクトを保存するために使用されます。つまり、ブロッカー変数は、パークメソッドを呼び出すスレッドのメンバー変数に保存されます。
4. void parknanos(オブジェクトブロッカー、ロングナノス)関数には、パーク(オブジェクトブロッカー)と比較して追加のタイムアウト時間があります。
5.Void Parkuntil(オブジェクトブロッカー、長期締め切り)Parkuntilのソースコードは次のとおりです。
public static void parkuntil(オブジェクトブロッカー、長期締め切り){thread t = thread.currentthread(); SetBlocker(T、ブロッカー); // isabsolute = true、time = deadline;それを意味しません。 SetBlocker(T、null); }それは設定された締め切りであり、時間単位はミリ秒であり、1970年から現在の時点までのミリ秒後に値に変換されることがわかります。これとパルナノス(オブジェクトブロッカー、ロングナノス)の違いは、後者が現在の時刻から待っているナノ時間を計算し、前者は時点を指定することです。
たとえば、2018.06.06の20:34まで待ってから、この時点を1970年からこの時点までのミリ秒の総数に変換する必要があります。
別の例を見てみましょう。コードは次のとおりです。
Import java.util.queue; Import java.util.concurrent.concurrentlinkedqueue; Import java.util.concurrent.atomic.atomicboolean; Import java.util.concurrent.locks.locksupport;/*** 2018/6/6で作成されたCongによって作成されました。 */public class fifomutex {private final atimicboolean locked = new Atomicboolean(false);プライベート最終キュー<スレッド>ウェイター= new concurrentlinkedqueue <thread>(); public void lock(){booleanは中断された= false;スレッドcurrent = thread.currentthread(); waiters.add(current); //チームのヘッドのスレッドのみがロック(1)を取得できます(1)while(waiters.peek()!= current ||!locked.compareandset(false、true)){locksupport.park(this); if(thread.interrupted())//(2)は中断= true; } waiters.remove(); if(aded interrurded)//(3)current.interrupt(); } public void lock(){locked.set(false); locksupport.unpark(waiters.peek()); }}これはファーストインストアウトロックであることがわかります。つまり、キューヘッダー要素のみが取得できることがわかります。コード(1)現在のスレッドがキューヘッダーではない場合、または現在のロックが他のスレッドによって取得されている場合は、Park Methodを呼び出して自分自身を掛けてください。
次に、コード(2)が判断を下します。パークメソッドが中断されて戻ってくると、割り込みが無視され、割り込みフラグがリセットされ、フラグのみが作成され、現在のスレッドがキューのヘッド要素であるか、ロックが他のスレッドによって取得されたかどうかを再度決定します。もしそうなら、自分自身を掛けるために公園の方法に電話をかけ続けてください。
次に、コード(3)でマークが真である場合、スレッドは中断されます。これをどのように理解していますか?実際、他のスレッドがスレッドを中断しました。私は割り込み信号に興味がなく、無視していますが、他のスレッドがフラグに興味がないという意味ではないので、復元する必要があります。
要約します
上記は、この記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。