Javaの並行プログラミングの戦闘仕上げノート

まず、セキュリティスレッド

    セキュリティスレッド、正しさの中核概念ではなく、意味の正しさは、次のとおりです。クラスの行為と正確にその仕様。種々の条件の下で定義されるような仕様では、おおよその結果が期待クラスのオブジェクトと一致しており、ここで理解することができます。シングルスレッドは、精度はおよそ「あなたは(私たちはそれを見たとき、我々はそれを知っている)が知られている見るもの」と定義することができます。複数のスレッドが常に正しい挙動を示すことができ、クラスにアクセスするときに、このクラスはスレッドセーフと考えることができます:「安全なセックス」の概念について明確では、我々は、スレッドの安全性がこれです考えることができますA。 

複数のスレッドがクラスにアクセスすると、関係なく、使用またはこれらのスレッドをスケジュールする動作環境の実行方法を交互になり、呼び出し元のコードに追加の同期的または相乗を必要としない、クラスが適切な挙動を示すことができます、その後、このクラスはスレッドセーフであると呼びます。

   スレッドセーフなクラスも同時環境として考えることができ、シングルスレッド環境では、クラスを中断されることはありません。シングルスレッド環境でのクラスはスレッドセーフではクラスではない場合、それは確かにスレッドセーフなクラスではありません。ここでは、スレッドセーフなクラスの例です。

public class StatelessFactorizer implements Servlet{
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        encodeIntoResponse(resp, factors);
    }
}
复制代码

   このStatelessFactorizerはステートレスである:それは、ドメイン内の他のクラスへの参照が含まれていない、どちらも任意のドメインが含まれています。ローカル変数の方法は、スレッドを実行することによってアクセスすることができます。複数のスレッドが同時にアクセスStatelessFactorizerがある場合は、それがあるかのようにアクセス異なるインスタンスに、スレッド間で共有状態が存在しないため、これらのスレッド間で相互に影響しません。

   スレッドの振る舞いは、オブジェクトにアクセスするので、ステートレスで、運転中に他のスレッドの有効性には影響しないのでステートレスオブジェクトは、スレッドセーフとしているスレッドセーフでなければなりませんステートレスオブジェクト。

第二に、アトミック

   それのアトミック性とは何ですか?原子性は事実であり、もはや分割自然、細かい粒度に分割することはできません。

   私たちは(どちらかのカウンタ)は一例であり、ステータスを追加する場合は、要求の数の統計情報が処理された、各リクエストの処理には値1、次の手順に追加されます。

public class StatelessFactorizer implements Servlet{
	private long count = 0;
	
	public long getCount(){return count;}
	
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;
        encodeIntoResponse(resp, factors);
    }
} 
复制代码

  上記のプログラム例では、一見、何の問題もなく、++操作のようなルックスを数えるが、インクリメント操作がアトミックではありません。一連の操作「 - - 修正書き込み読み」:実際には、それは3つの操作が含まれているため。各操作は、フロント前の状態に依存しています。この時点で2つのスレッドが存在する場合、Aのスレッドがこの時の動作を変更することであった場合、Aは、Bは、読み出しスレッドBは、最終的なAは、Bのスレッドは、値が同じで書き込むのであれば、その期待される結果からの逸脱1。 

  これが見えますが、そのうちのいくつかは、許容可能な結果から外れるかもしれないが、カウンタ値の値が配列または一意のオブジェクト識別子を生成するために使用されている場合、同じ値が複数の呼び出し深刻になります返すデータ整合性の問題。

  これは非常に重要な場合は、誤った結果の出現の不適切な実行タイミングにあると並行プログラミングでは、このような状況は、「競合状態(競合状態)」と呼ばれています。

レース条件

  計算際に精度が交互に複数のスレッドの実行のタイミングに依存して発生する競合状態。だけでなく、操作の競合状態「最初のチェックの実装」の一般的なタイプ、失敗する可能性の観測によって、次のアクションを決定します

  栗のために:あなたは彼を待ってカフェに滞在することを選択するかもしれないあなたが行く場合は、あなたとあなたの友人が黒カフェでオープンへ行く約束をした、あなたがカフェを取得し、私はあなたの友人ではない見つけ、彼は、彼を探して彼の家に行くことができます彼はあなたが彼の方法を見つけるために彼の家に行く可能性があるため、失敗する可能性があります(友人ではない)あなたはインターネットカフェでの観測後にカフェで出ているときに、彼を見つけるには、カフェに来ましたが、あなたは彼のために見ています。

  栗は、結果は(あなたがバーで会った)正しいのですが、結果は(両方とも長いと誰カフェに反対側のを待つ必要があります)イベントのタイミングに依存しています。計算や判断を行うに失敗する可能性の観測結果に基づいて - この観察の失敗は、ほとんどの競合状態の性質ということです。

  別の栗のように、それはスレッドが検出した場合、フォルダが存在しないことを条件とする、存在しない場合、ファイルを作成することを計画して作成し、二つのスレッドA、B、Aが存在し、Bのスレッドがフォルダが存在するかどうかを決定するために使用されていると仮定フォルダが、Bのスレッドがフォルダの作成が完了しているこの時間は、その後、今回の観測結果は、スレッドが期限切れになっているだろうが、スレッドがまだこの観察に基づいているにつながる可能性が次の動き、中に有効期限が切れ様々な問題。

「最初の実行後にチェックし、」一般的な使用例は、遅延初期化です。例えば以下のように、一の実施形態の単一モードであり文言であろう。

public class LazyInitRace {
	private static LazyInitRace instance = null;
	
	public LazyInitRace getInstance(){
		if(instance == null){
			instance = new LazyInitRace();
		}
		return instance;
	}
} 复制代码

  これはA、Bのスレッドが実行のgetInstance()メソッドが存在する場合、その結果は期待に沿っていてもよい、あなたは、2つの異なるを得ることができ、シングルスレッドの書き込み何も間違ったではなく、マルチスレッド環境では、典型的な遅延初期化されこれは、オブジェクト。スレッドinstace発見がヌルであるため、Bのスレッドもinstaceがヌルであるかもしれません。

  ほとんどの同時実行エラーで、競合状態が常にエラーを生じないように、あなたは実行の不適切なタイミングのいくつかの種類が必要ですが、問題が発生した場合、それは非常に深刻な問題につながる可能性があります。

  上記の例では、アクションのセットを含むアトミックに行う(または積分)する必要があります。レースコンディションの問題を回避するためには、スレッド、とき変数を変更する必要がある他のスレッドを防ぐために、いくつかの方法が読む前または変更することができ、他のスレッドを確保するために、この変数を使用する操作がではなく、状態の変更完了後変更プロセス

  上記の統計は数の要求の例を処理しているとき、私たちはAtmoicLongクラスはスレッドセーフなクラスですので、あなたが例も例安全である保証はなく、状態変数を追加することができますので、長期使用のAtomicLongオブジェクトを置き換えるかどうか、使用することもできますすることができますそれを管理し、その状態のスレッドの安全性を維持するために、オブジェクトのスレッドセーフなクラス?次のとおりです。

public class UnsafeCachingFactorizer implements Servlet {
	private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>();
 
	private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<>();
 
	public void service(ServletRequest req, ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		if (i.equals(lastNumber.get())) {
			encodeIntoResponse(resp, lastFactors.get());
		} else {
			BigInteger[] factors = factor(i);
			lastNumber.set(i);
			lastFactors.set(factors);
			encodeIntoResponse(resp, factors);
		}
	}
} 复制代码

上記の例では、2つの変数は、スレッドセーフがありますが、競合状態は、上記の例では、クラス不変条件が破壊されていることから、これだけの条件が不変ではないことを保証するために、サービスメソッドでまだそこにあります破壊され、それが正しいです。不変条件は、複数の変数に関連する場合、独立していない間の各変数が、他の変数の値が別の制約世代の変数の値を有します。したがって、特定の変数の更新は、同一のアトミック操作で他の変数を更新する必要がある場合。

   上記の例では、がアトミック動作が設定されているが、同時にlastFactors lastNumberおよびセット方法を更新することができません。この時点でサービスメソッドにアクセスするためのスレッドがある場合は、スレッドの実行時にlastNumber.set()メソッドは、次の設定方法を実行していない場合、結果は我々が期待したものとの矛盾を取得します。

したがって、状態の一貫性を維持するために、単一のアトミック操作に関連するすべての状態変数を更新する必要があります。

第三に、ロック機構

内蔵ロック3.1

   同期コードブロック(同期ブロック):原子をサポートするためのJava内蔵ロック機構を提供します。シンクブロックは2つの部分からなる:一つはオブジェクトロックへの参照であり、このブロックは、コード保護としてロックから構成されています。オブジェクト(これは)。静的メソッド、クラスオブジェクトはロックとして同期されるブロック同期コードの呼び出しが行われる方法で同期キーワードを修正するためにロックするための方法は、全身に同期コードブロックの方法です。 

   すべてのJavaオブジェクトを達成するために、同期ロックとして使用することができ、これらのロックは、内蔵のロック(固有ロック)ロックやモニター(モニターロック)と呼ばれています。スレッドを自動的に同期ブロックに入る前にロックし、あなたがsynchronizedブロックを終了するときに自動的にロックを解除します。

   Javaの組み込みの相互排他のロックと同等一つだけのスレッドがこのロックを保持できるロックアップスレッドAスレッドBがロックのホールドを取得しようとすると、ブロックしたり、スレッドAは、スレッドBのリリースにロックを知るために待たなければなりません。スレッドBがロックを解除しない場合は、スレッドAは永遠に待機します。いずれかのスレッドが同期ブロックを実行し、他のスレッドが同じロックによって保護された同期ブロックを実行している見ることができません。

内蔵のロックのアプリケーションの例を示します。

public class SynchronizedFactorizer implements Servlet {
	private BigInteger lastNumber;
 
	private BigInteger[] lastFactors;
 
	public synchronized void service(ServletRequest req, ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		if (i.equals(lastNumber)) {
			encodeIntoResponse(resp, lastFactors.get());
		} else {
			BigInteger[] factors = factor(i);
			lastNumber = i;
			lastFactors = factors;
			encodeIntoResponse(resp, factors);
		}
	}
} 复制代码

   結果の正しさを保証するために、キーワードsynchrnoized 1つのだけのスレッドがサービスの非常に低い応答性へのリードは、同時性が非常に悪いと同時に、サービスの方法を実行することができますが、それはパフォーマンスの問題ではなくなり、スレッド安全性の問題。

3.2リエントラント

   スレッドがロックが別のスレッドによって保持されている要求すると、要求元のスレッドがブロックされます、しかし、内蔵のロックのためには、再入可能スレッドは、独自のホールドを取得しようとしてきた場合、つまり、ロックは、その要求が成功しますとき。「再入国は、」ロックの粒度を取得する操作を意味するのではなく、「スレッド」である「呼び出し。」A法は、ロック所有者とスレッドのカウント値に関連付けられたそれぞれリエントラントです。カウント値が0であるとき、私はこのロックがいずれかのスレッドにより保持されていないと思います。スレッドはロックが保持されていない要求すると、JVMは、ロックホルダに注意し、取得したカウント値が1に設定されています。あなたは、スレッドカウント値を再びロックを取得する場合はインクリメントされ、スレッドがsynchronizedブロックを終了すると、カウント値がそれに応じて減少します。カウント値が0の場合、ロックが解除されます。 

以下は再進入の一例です。

public class Widget{
    public synchronized void doSomething(){
        System.out.println(toString() + ": calling doSomething");
    }
}
public class LoggingWidget extends Widget{
    public synchronzied void doSomething(){
           System.out.println(toString() + ": calling doSomething");
           super.doSomething();
    }
} 复制代码

オブジェクトがdoSomethingのメソッドを呼び出すサブクラスである場合、上記の例では、LoggingWidgetは、ウィジェットを継承し、親クラスを書き換え、キーワード変更doSomethingの方法で同期されます。ノーリエントラントロックの場合、このコードはデッドロックします。意志を実行する前に、ウィジェットのロックを取得するために、各doSomethingの道は、内蔵された場合、ロックはリエントラントではありませんので、あなたは、このロックアップ開催されているので、super.doSomethingがそうスレッドという、ウィジェットのロックを取得することはできません呼び出すとき私はいつも決して得られないロックを待って、一時停止になります。注:ここではsynchronizedキーワードはsuper.doSomethingを呼び出し、それはオブジェクト自体(この)あなたが最初のdoSomethingのメソッドを入力したときに、そう、ロックされているLoggingWidgetオブジェクトをロックされている意味メソッド本体を、変更されました親は、新しいクラスのオブジェクト、オブジェクトを作成したり、これをロックしていないとき。 

第四には、使用状態を保護するためのロック

   あなたはそれが、この場合には、同じロックを保持する必要がアクセスしたときに、変数の状態変数は、複数のスレッドから同時にアクセスすることができるために、我々はこの状態変数は、ロック保護で呼び出します。物体との間には固有の関連その内蔵ロック状態、ドメインオブジェクトは、必ずしも内蔵ロックによって保護されていません。オブジェクトのロックに関連付けられているを取得する際、他のスレッドは、スレッドのロックオブジェクトを取得した後にオブジェクトにアクセス妨げないだけ同じロックを取得するために他のスレッドを防ぐことができ、各オブジェクトが組み込まれているロック。 

   各共有変数と変数は、ロックによって保護されなければなりません。一般的な慣例がロックされ、状態変数は、すべてのオブジェクト内にカプセル化され、すべてのアクセスコードロードキングを同期ビルトイン変数の状態をロックオブジェクトによって、同時アクセスがオブジェクトでは発生しないように。しかし、あなたは新しいメソッドを追加したり、同期コードパスを使用する場合は、この契約はロックされます忘れてしまった場合、容易に破壊することができます。

   我々は、すべてのデータが保護ロック、唯一それがロックによって保護される必要があり、同時に複数のスレッドによってアクセスされる変数のデータを必要としていることを知っている必要があります。変数をロックによって保護されている場合は、最初にこの変数にアクセスするたびに、これだけのスレッドが同時に変数にアクセスできることを確実にすることをロック手段を取得する必要があります。不変条件はクラスの複数の状態変数に関連すると、その後、別の需要があります。各変数の不変条件が同じロックによって保護されなければなりません。 

   同期は、レースコンディションの問題を回避するために、それが意味するものではありませんが、あなたがキーワードが同期されている使用することができます。あまりにも多くの同期化方法のプログラムがある場合は、活動が問題やパフォーマンスの問題の各メソッド宣言が発生することがあります。

   私たちは、共有状態に試してみてくださいとだけアトミック操作があることを保証するために、可能な限りブロックから同期コード、同期コード・ブロックを分離操作を実行するための時間には影響しません。

   使用時にはロックは、コードブロックに実現される機能をオフにすること、そしてそれは、長時間場合、コードブロックの実行を要するか否かなければならない操作の長い計算時間や実行が迅速に完了されなくてもよい場合(例えば、ネットワークI / OまたはコンソールI / O)は、ロックを保持していなければなりません!


おすすめ

転載: juejin.im/post/5de7a9876fb9a016402d06af