Java並行プログラミング学習3-可視性とオブジェクトリリース

オブジェクト共有

この本の最後の部分から、同期によって複数のスレッドが同じデータに同時にアクセスするのを防ぐ方法を学びました。このパートでは、オブジェクトを共有および公開して、同時に複数のスレッドから安全にアクセスできるようにする方法を紹介します。時間。

1.可視性

スレッドセーフの内容については、同期されたコードブロックと同期メソッドにより、操作がアトミックに実行されることを保証できることをお知らせください。しかし、同期されたキーワードは、原子性を達成するため、または「クリティカルセクション(クリティカルセクション)」を決定するためにのみ使用できると考える場合、あなたは非常に間違っています。同期にはもう1つの重要な側面があります。それは、メモリの可視性です。別のスレッドが同時に状態を変更しているときにスレッドがオブジェクトの状態を使用しないようにするだけでなく、あるスレッドがオブジェクトの状態を変更したときに、他のスレッドが発生した状態の変化を確認できるようにする必要もあります。

可視性は複雑な属性です。一般的なシングルスレッド環境では、最初に変数に値を書き込んでから、他の書き込み操作なしで変数を読み取ると、常に同じ値が得られます。ただし、読み取り操作と書き込み操作が異なるスレッドで実行される場合、読み取り操作を実行するスレッドが他のスレッドによって書き込まれた値をタイムリーに認識できることを保証できないため、常に同じ値が得られるとは限りません。複数のスレッド間のメモリ書き込み操作の可視性を確保するには、同期メカニズムを使用する必要があります。

多くのことを紹介したので、コード例を見てみるとよいでしょう。

/**
 * <p> 在没有同步的情况下共享变量(不推荐使用) </p>
 */
public class NoVisibility {
    
    
    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
    
    
        @Override
        public void run() {
    
    
            while (!ready) {
    
    
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
    
    
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

上記のコードでは、メインスレッドとリーダースレッドの両方が共有変数readynumberにアクセスしますメインスレッドはリーダースレッドを開始し、数値を42に設定、trueに設定します。リーダースレッドはreadyの値がtrueになるまでループを続けnumberの値を出力します。NoVisibilityは42を出力しているように見えますが、実際には0を出力する可能性が非常に高いか、まったく終了できません。コードで使用される同期メカニズムが十分でないため、メインスレッドによって書き込まれたready値とnumber値がリーダースレッドに表示される保証はありません

プログラムを実行しようとすると、コンソールが42を出力する可能性が高くなりますが、これは、このコードが常に目的の結果を出力することを意味するわけではありません。NoVisibilityリーダースレッドが参照することができるので、出力してもよい0これはあるに書き込む準備をするが、見ていないの値数は後で書き込まこの現象は、「並べ替え」と呼ばれている; NoVisibilityは、ループし続けることができる、リーダ・スレッドは可能性があるためreadyの値は決して表示されません

同期がないと、コンパイラ、プロセッサ、およびランタイムが、操作の実行順序に予期しない調整を​​加える可能性があります。十分な同期が取れていないマルチスレッドプログラムでは、メモリ操作の実行順序を判断するために正しい結論を出すことはほとんど不可能です。

1.1故障データ

NoVisibilityは、同期が不足しているプログラムで誤った結果を生成する可能性がある状況、つまり無効なデータを示しています。リーダースレッドがready変数を調べると、無効な値を取得する可能性があります。さらに悪いことに、無効な値が同時に表示されない場合があります:スレッドが変数の最新の値を取得し、別の変数の無効な値を取得する可能性があります。

別のコード例を見てみましょう。

/**
 * <p> 非线程安全的可变整数类 </p>
 */
@NotThreadSafe
public class MutableInteger {
    
    
    private int value;

    public int getValue() {
    
     return value; }
    public void setValue(int value) {
    
     this.value = value; }
}

上記のコードgetメソッドsetメソッドはどちらも、同期なしで値にアクセスしますスレッドがsetメソッドを呼び出す場合、getメソッドを呼び出している別のスレッドは、更新された値を表示する場合と表示しない場合があります

次に、getメソッドsetメソッドを同期することで、MutableIntegerをスレッドセーフなクラスにすることができますコード例は次のとおりです。

/**
 * <p> 非线程安全的可变整数类 </p>
 */
public class SynchronizedInteger {
    
    
    @GuardedBy("this") private int value;

    public synchronized int getValue() {
    
     return value; }
    public synchronized void setValue(int value) {
    
     this.value = value; }
}

もちろん、ここsetメソッドのみを同期するだけでは不十分であり、getメソッドを呼び出すスレッドは引き続き無効な値を認識します。

1.2非アトミック64ビット演算

スレッドが同期せずに変数を読み取ると、無効な値を取得する可能性があることを上で学びましたが、少なくともこの値は前のスレッドによって設定された値であり、ランダムな値ではありません。この種の安全保証は、空中安全とも呼ばれます。

最小限のセキュリティは、非揮発性の64ビット数値変数を除くほとんどの変数に適用されます。Javaメモリモデルでは、変数の読み取りおよび書き込み操作はアトミック操作である必要がありますが、非揮発性タイプのlong変数およびdouble変数の場合、JVM64は2つの32ビット操作への読み取りまたは書き込み操作を許可します。不揮発性タイプの長い変数を読み取るときに、変数の読み取り操作と書き込み操作が異なるスレッドで実行されると、値の上位32ビットと、下位32ビットに相当する他のビットが読み取られる可能性があります。

1.3ロックと可視性

次の図に示すように、組み込みロックを使用して、スレッドが別のスレッドの実行結果を予測可能な方法で表示できるようにすることができます。スレッドAが同期コードブロックを実行すると、スレッドBは同じロックで保護された同期コードブロックに入ります。この場合、ロックが解放される前に、Aが見る変数値はBがロックを取得した後であることを保証できます。同じことがBでも見ることができます。つまり、スレッドBがロックで保護された同期コードブロックを実行すると、同じ同期コードブロックでスレッドAの以前のすべての操作の結果を確認できます。

ロックの意味は、相互に排他的な動作に限定されるだけでなく、メモリの可視性も含みます。すべてのスレッドが共有変数の最新の値を確認できるようにするには、読み取りまたは書き込み操作を実行するすべてのスレッドが同じロックで同期する必要があります。

1.4揮発性変数

Java言語は、変数の更新操作が他のスレッドに確実に通知されるように、より弱い同期メカニズム、つまり揮発性変数を提供します変数が揮発性として宣言されると、コンパイラとランタイムは変数が共有されていることを認識します。そのため、変数に対する操作は他のメモリ操作と並べ替えられません。揮発性変数はレジスタにキャッシュされたり、他のプロセッサからは見えないため、揮発性変数を読み取るときは、常に最新の書き込み値が返されます。

もちろん、揮発性変数によって提供される可視性に過度に依存することはお勧めしません揮発性の時間変数が実装と検証コードの同期戦略を簡素化できる場合にのみ、それらを使用する必要があります。正確性を検証するときに可視性について複雑な判断を下す必要がある場合は、揮発性変数を使用することをお勧めします。

揮発性変数の正しい使用法は次のとおりです。

  1. 自分の状態の可視性を確保します。
  2. それらが参照するオブジェクトの状態の可視性を確保します。
  3. いくつかの重要なプログラムライフサイクルイベント(初期化またはシャットダウン)の発生を特定します

揮発性変数を使用して羊を数えるコード例を見てみましょう

volatile boolean asleep;
// ...
	while (!asleep) 
		countSomeSheep();

上記の例では、スレッドは羊を数えるのと同様の従来の方法で休止状態に入ろうとします。スリープ状態の更新操作の可視性を確保するためにロックを使用する場合と比較して、ここでの揮発性変数の使用は、更新操作の可視性を満たすだけなく、コードロジックがより単純で理解しやすくなります。

が、揮発性の変数を使用することは非常に便利です、彼らは唯一の視認性を確保することができ、かつロック機構は、可視性とアトミック性の両方を確保することができます。

そうは言っても、どのような状況で揮発性変数を使用する必要がありますか?

次の条件が満たされている場合に限ります。

  • 変数への書き込み操作は、変数の現在の値に依存しません。または、単一のスレッドのみが変数の値を更新するようにすることができます。
  • この変数は、他の状態変数とともに不変条件に含まれません。
  • 変数にアクセスするときにロックする必要はありません。

2.解放して脱出する

ベンピアンが言及した公開オブジェクトから始めて、それは現在の範囲外で使用できるオブジェクトコードを指します。たとえば、他のコードがアクセスできるオブジェクトへの参照を保存するか、非プライベートメソッドで参照を返すか、別のクラスのメソッドに参照を渡します。オブジェクトが公開されるべきではないリリースがリリースされた場合、この状況はエスケープ(エスケープ)と呼ばれます。

オブジェクトを公開する最も簡単な方法は、オブジェクトへの参照をパブリック静的変数に保存して、すべてのクラスとスレッドがオブジェクトを表示できるようにすることです。

以下に、オブジェクトを公開するためのコード例を示します。

	public static Set<Secret> knownSecrets;
	
	public void initialize() {
    
    
		knownSecrets = new HashSet<Secret>();
	}

上記のコードでは、新しいHashSetオブジェクトがinitializeメソッドでインスタンス化さ、オブジェクトの参照がknownSecrets保存されて、オブジェクトが公開されます。場合シークレットオブジェクトがコレクションに追加されたknownSecrets秘密のオブジェクトはまた、公開されます任意のコードは、このコレクションをトラバースして得ることができるので、参照この新しいの秘密のオブジェクトを。

別のコード例を見てみましょう。

/**
 * <p> 使内部的可变状态逸出(不推荐使用) </p>
 */
public class UnsafeStates {
    
    
    private String[] states = new String[] {
    
    "HELLO", "HUAZIE"};

    public String[] getStates() {
    
     return states; }
}

アピールコード、非プライベートメソッドgetStatesから参照返します。ここで、返された参照オブジェクトの状態も公開されます。上記の方法で状態を公​​開する呼び出し元がこの配列の内容を変更できるため、非常にリスクが高くなる可能性があります。

公開されたオブジェクトが非プライベート変数参照とメソッド呼び出しを介して他のオブジェクトに到達できる場合、これらのオブジェクトも公開されます。

次のコード例に示すように、オブジェクトまたはその内部状態を公開するための最後のメカニズムは、内部クラスインスタンスを公開することです。

/**
 * <p> 隐式地使this引用逸出(不推荐使用) </p>
 */
public class ThisEscape {
    
    
    public ThisEscape(EventSource source) {
    
    
        source.registerListener(new EventListener(){
    
    
			public void onEvent(Event e) {
    
    
				doSomething(e);
			}
		});
    }

    private void doSomething(Event e) {
    
    
        // 事件处理
    }
}

場合ThisEscapeリリースイベントリスナーは、それはまた、暗黙的に解放ThisEscapeのこの内部クラスのインスタンスが含まれているため、インスタンス自体を暗黙基準ThisEscapeのインスタンス。

安全なオブジェクト構築プロセス

エスケープの特別な例がThisEscapeに示されています。つまり、この参照はコンストラクターでエスケープされます。この参照が構築中にエスケープされた場合、そのようなオブジェクトは正しく構築されていないと見なされます。

:構築中にこの参照をエスケープしないでください

イベントリスナーを登録したり、コンストラクターでプロセスを開始したりする場合は、プライベートコンストラクターとパブリックファクトリメソッドを使用して、誤った構築を回避できます。以下のコード例を参照してください。

/**
 * <p> 使用工厂方法来防止this引用在构造过程中逸出 </p>
 */
public class SafeListener {
    
    
    private final EventListener listener;

    private SafeListener() {
    
    
        listener = new EventListener(){
    
    
			public void onEvent(Event e) {
    
    
				doSomething(e);
			}
		};
    }

    public static SafeListener newInstance(EventSource source) {
    
    
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }

    private void doSomething(Event e) {
    
    
        // 事件处理
    }
}

結論

この記事では、オブジェクトの可視性とリリースとエスケープについて学習しました。オブジェクト共有に関するその他のコンテンツ[スレッドの閉鎖、不変性、安全なリリース]については、紹介を終了するためにブログ投稿が必要ですので、ご期待ください。

おすすめ

転載: blog.csdn.net/u012855229/article/details/114262247