ロックフリーの概念は、[高い並行性Java 1]の導入で言及されています。 JDKソースコードには多数のロックフリーアプリケーションがあるため、ここにロックフリーが紹介されています。
1ロックレスクラスの原則の詳細な説明
1.1 CAS
CASアルゴリズムのプロセスは次のとおりです。3つのパラメーターCAS(V、E、N)が含まれています。 vは更新する変数を表し、eは期待値を表し、nは新しい値を表します。 vの場合のみ
値がe値に等しい場合、vの値はNに設定されます。V値がe値と異なる場合、他のスレッドはすでに更新を行っており、現在のスレッドは何もしません。最後に、CASは現在のVの真の価値を返します。CAS運用は楽観的な態度で実行され、操作を正常に完了できると常に考えています。複数のスレッドが同時にCASを使用して変数を動作させると、1つだけが勝ち、更新され、残りは失敗します。故障したスレッドは中断されず、障害が許可されていることのみが通知され、再試行することが許可されており、もちろん失敗したスレッドも操作を放棄することを可能にします。この原則に基づいて、CAS
操作はすぐにロックされ、他のスレッドは現在のスレッドへの干渉を検出し、適切に処理することもできます。
CASにはあまりにも多くのステップがあることがわかります。 VとEが同じであると判断した後、値を割り当てようとしているときに、スレッドを切り替えて値を変更した可能性がありますか?データの矛盾の原因は何ですか?
実際、この心配は冗長です。 CASの動作プロセス全体は原子操作であり、CPU命令によって完了します。
1.2 CPUの指示
CASのCPU命令はCMMPXCHGです
命令コードは次のとおりです。
/ * Accumulator = al、ax、またはeax、バイト、ワード、またはダブルワードの比較が実行されているかどうかに応じて */ if(acumulator == destination){zf = 1;宛先=ソース; } else {zf = 0; Accumulator = Destination; }ターゲット値がレジスタの値に等しい場合、ジャンプフラグが設定され、元のデータがターゲットに設定されます。待たなければ、ジャンプフラグを設定しません。
Javaは多くのロックフリークラスを提供しているため、以下にロックフリークラスを紹介しましょう。
2役に立たない
ロックフリーはブロッキングよりもはるかに効率的であることをすでに知っています。 Javaがこれらのロックフリークラスをどのように実装するかを見てみましょう。
2.1。 Atomicinteger
AtomicInteger、整数のように、両方とも数字クラスを継承します
Public Class AtomicIntegerは、java.io.serializableを実装する数字を拡張します
Atomicintegerには多くのCAS操作があり、その典型的なものは次のとおりです。
Public Final Boolean CompareAndset(int expect、int update){
unsafe.compareandswapintを返します(this、value offset、expect、update);
}
ここでは、unsafe.compareandswapintメソッドについて説明します。つまり、このクラスのオフセットがValueOffsetである変数の値が期待値と同じである場合、この変数の値を更新するように設定します。
実際、オフセット値オフセットを持つ変数は値です
static {try {valueoffset = unsafe.objectfieldoffset(atomicinteger.class.getDeclaredfield( "value")); } catch(Exception ex){throw new error(ex); }}以前にはCASが失敗する可能性があると言ってきましたが、失敗のコストは非常に少ないため、一般的な実装は成功するまで無限のループにあります。
public final int getandincrement(){for(;;){int current = get(); int next = current + 1; if(compareandset(current、next))return current; }}2.2安全でない
クラス名から、危険な操作は次のような安全でない操作であることがわかります。
オフセットに従って値を設定します(導入されたAtomicintegerでこの機能を見ました)
park()(このスレッドを停止してください、将来のブログで言及されます)
基礎となるCAS操作以外のAPIは、JDKの異なるバージョンで大きく異なる場合があります。
2.3。アトミックレファレンス
Atomicintegerは以前に言及されており、もちろんAtomicboolean、Atomiclongなどはすべて似ています。
ここで紹介したいのはAtomicReferenceです。
AtomicReferenceはテンプレートクラスです
Public Class AtomicReference <V>はjava.io.serializableを実装します
あらゆる種類のデータをカプセル化するために使用できます。
たとえば、文字列
パッケージテスト; Import java.util.concurrent.atomic.atomicreference; public class test {public final static AtomicReference <string> AtomicString = new AtomicReference <String>( "Hosee"); public static void main(string [] args){for(int i = 0; i <10; i ++){final int num = i; new shood(){public void run(){try {thread.sleep(math.abs((int)math.random()*100)); } catch(Exception e){e.printstacktrace(); } if(AtomicString.comPareandset( "Hosee"、 "ztk")){system.out.println(thread.currentthread()。getId() + "change value"); } else {system.out.println(thread.currentthread()。getId() + "failed"); }}; }。始める(); }}}結果:
10フェイル
13フェイル
9change値
11failed
12フェイル
15フェイル
17failed
14フェイル
16Failed
18failed
1つのスレッドのみが値を変更できることがわかり、後続のスレッドはこれ以上変更できません。
2.4. AtomicStampedReference
CASの操作にはまだ問題があることがわかります。
たとえば、以前のAtomicInteger IncrementAndgetメソッド
public final int incrementAndget(){for(;;){int current = get(); int next = current + 1; if(compareandset(current、next))は次を返します。 }}スレッドint current = get()が実行され、別のスレッドに切り替えると、現在の値= 1が、このスレッドが2に変わり、別のスレッドが再び2に変わります。この時点で、最初のスレッドに切り替えます。値はまだ1に等しいため、CAS操作はまだ実行できます。もちろん、追加に問題はありません。いくつかのケースがある場合、データの状態に敏感な場合、そのようなプロセスは許可されません。
この時点で、AtomicStampedReferenceクラスが必要です。
ペアクラスを内部的に実装して、値とタイムスタンプをカプセル化します。
private static classペア<t> {final t Reference;最終的なINTスタンプ。プライベートペア(tリファレンス、intスタンプ){this.reference = reference; this.stamp = stamp; } static <t> pair <t> of(t reference、int stamp){return new pair <t>(参照、スタンプ); }}このクラスの主なアイデアは、各変更を特定するためにタイムスタンプを追加することです。
//設定の比較パラメーターは次のとおりです。
public boolean CompareAndset(v expectionReference、v newReference、int spedteseStamp、int newstamp){pair <v> current = pair; return expectReference == current && spectedstamp == current.stamp &&((newReference == current.reference && newStamp == current.stamp)|| caspair(current、pair.of(newReference、newStamp))); }期待値が現在の値に等しく、予想されるタイムスタンプが現在のタイムスタンプに等しい場合、新しい値が記述され、新しいタイムスタンプが更新されます。
これは、AtomicStampedReferenceを使用するシナリオです。適切ではないかもしれませんが、良いシナリオを想像することはできません。
シーンの背景は、会社がバランスが少ないユーザーを無料で充電することですが、各ユーザーは1回しか充電できません。
パッケージテスト; java.util.concurrent.atomic.atomicstampedreference; public class test {static atomicStampedReference <integer> money = new AtomicStampedReference <integer>(19、0); public static void main(string [] args){for(int i = 0; i <3; i ++){final int timestamp = money.getStamp(); new shood(){public void run(){while(true){while(true){integer m = money.getReference(); if(m <20){if(money.compareandset(m、m + 20、timestamp、timestamp + 1)){system.out.println( "recharge resecly、balance:" + money.getReference());壊す; }} else {break; }}}}}}; }。始める(); } new shood(){public void run(){for(int i = 0; i <100; i ++){while(true){int timestamp = money.getStamp(); integer m = money.getReference(); if(m> 10){if(money.compareandset(m、m -10、timestamp、timestamp + 1)){system.out.println( "消費10 yuan、balance:" + money.getreference());壊す; }} else {break; }} try {thread.sleep(100); } catch(Exception e){// TODO:例外を処理}}}}; }。始める(); }}コードを説明すると、3つのスレッドがユーザーを充電します。ユーザーの残高が20未満の場合、ユーザー20元を充電します。 100個のスレッドが消費されており、それぞれが10元を費やしています。ユーザーは最初に9元を持っています。 AtomicStampedReferenceを使用してそれを実装する場合、各操作がタイムスタンプ+1を引き起こすため、ユーザーは一度だけ充電されます。実行結果:
充電に成功し、残高:39
10元の消費、バランス:29
10元の消費、バランス:19
10元の消費、バランス:9
AtomicReference <integer>またはAtomic Integerを使用して実装すると、複数の充電が発生します。
充電に成功し、残高:39
10元の消費、バランス:29
10元の消費、バランス:19
充電に成功し、残高:39
10元の消費、バランス:29
10元の消費、バランス:19
充電に成功し、残高:39
10元の消費、バランス:29
2.5。 AtomicintegerArray
AtomicIntegerと比較して、配列の実装は追加のサブスクリプトにすぎません。
パブリックファイナルブールの比較(int i、int expect、int update){
return compareandsetraw(checkedbyteoffset(i)、expect、update);
}
その内部は、通常の配列をカプセル化するだけです
プライベートファイナルint []アレイ。
ここで興味深いのは、バイナリ番号の主要なゼロが配列のオフセットを計算するために使用されることです。
Shift = 31 -Integer.NumberOfLeadingzeros(スケール);
主要なゼロは、たとえば8ビットが12,00001100を表すことを意味し、その後、先行ゼロは1の前の0の数、つまり4です。
オフセットを計算する方法はここには紹介されていません。
2.6。 Atomicintegerfieldupdater
Atomicintegerfieldupdaterクラスの主な機能は、通常の変数が原子動作を享受できるようにすることです。
たとえば、もともとはINTタイプである変数があり、この変数は多くの場所で適用されました。ただし、特定のシナリオでは、INTタイプをAtomicIntegerに変換する場合は、タイプを直接変更する場合は、他の場所でアプリケーションを変更する必要があります。 Atomicintegerfieldupdaterは、そのような問題を解決するように設計されています。
パッケージテスト; Import java.util.concurrent.atomic.atomicinteger; import java.util.concurrent.atomic.atomicintegerfieldupdater; public class test {public static class v {int id;揮発性INTスコア。 public int getScore(){return score; } public void setScore(intスコア){this.score = score; }} public final static atimicintegerfieldupdater <v> vv = atomicintegerfieldupdater.newupdater(v.class、 "score"); public Static AtomicInteger AllScore = new AtomicInteger(0); public static void main(string [] args)throws arturtedexception {final v stu = new V();スレッド[] t = newスレッド[10000]; for(int i = 0; i <10000; i ++){t [i] = new shood(){@override public void run(){if(math.random()> 0.4){vv.incrementandget(stu); allscore.incrementAndget(); }}}; t [i] .start(); } for(int i = 0; i <10000; i ++){t [i] .join(); } system.out.println( "score ="+stu.getscore()); system.out.println( "allscore ="+allscore); }}上記のコードは、AtomicIntegerFieldUpdaterを使用してAtomicIntegerにスコアを変えます。スレッドの安全性を確保します。
ここでは、すべてのスコアが使用されています。スコアとすべてのスコア値が同じ場合、それはスレッドセーフであることを意味します。
注記: