Javaのマルチスレッドプログラミング(3) - スレッドセーフ

I.スレッドの安全性

  一般的には、シングルスレッド環境の場合、クラスが適切に機能する場合、および消費者がその機能を変更する必要はありませんここで、マルチスレッド環境で、我々はスレッドセーフそれを呼び出すことができます。逆に、クラスの機能が正常にシングルスレッド環境で、このクラスはスレッドセーフではありません、マルチスレッド環境で正常に機能することはできません。クラスは競合状態につながることができればこのように、それはスレッドセーフではありません、そしてクラスはスレッドセーフであるならば、それはレースにはなりません。以下は、指定されたスレッドセーフな本のために定義された「戦闘でのJava並行処理」です。

複数のスレッドがクラスにアクセスすると、スケジューリングランタイム環境の関係なく、どのような使用またはこれらのスレッドが実行を交互にする方法、および任意の追加の同期や調整を必要とせず、コード内のクラスは、正しい動作を表示することができますその後、このクラスはスレッドセーフであると呼びます。

  私たちは、このクラスはスレッドセーフであるかどうかを把握しなければならないとき、クラスを使用します。それは我々がこれらのクラスは、正しい使用方法に関連します。このようArrayListの、HashMapのとSimpleDateFormatのようなJavaの標準ライブラリクラスは、直接彼らはいくつかの予期しない結果、さらにいくつかの悲惨な結果につながる可能性がマルチスレッド環境では、非スレッドセーフです。一般的には、そのAPIドキュメントに含まれるJavaの標準ライブラリクラスは、それがスレッドセーフであるかどうかを示します(これは、スレッドセーフであるかどうかを指定していない、それがまたはスレッドセーフであってもなくてもよいです)。
  スレッドセーフなクラスは、マルチスレッド環境で正しく動作するかどうか、我々は、見ることができますスレッドセーフな定義から、それは、シングルスレッド環境で正常に機能することができます。スレッドセーフなされた場合であることことを、なぜちょうどクラスのすべてを入れませんか?、一方では、このクラスのスレッドセーフと予想される使用されるクラスの必要性についてです、例えば:ある程度、クラスのスレッドセーフを行うかどうかは、結果や設計上の決定を圧迫することです私たちは常にクラスは独自のスレッドでのみ使用することができることを願って、このクラスのスレッドセーフにする必要はありません。第二に、スレッドセーフなクラスを作るためには、多くの場合、追加のコストです。
  クラスはスレッドセーフではない場合、我々はそれがスレッドの安全性の問題は、マルチスレッド環境で直接存在して使用しますと言います。アトミック、可視性と秩序:スレッド安全性の問題の概要は、三つの側面を示しました。

II。不可分

  文字通り原子は不可分です。実行のスレッド以外のスレッドからの操作は、ビューの積分点である場合、共有変数へのアクセスを伴う操作の場合、この操作は、それに応じて、我々は、この操作はアトミックで呼び出し、アトミック操作です。ビューの実行ポイントのスレッド以外のスレッドから操作する共有変数にアクセスすることを意味しているの1、「不可分」いわゆる、この操作は、他のスレッドがアクションを「見る」しないこと、のいずれか終了か発生していない行われています中間部分の影響が行われます。
  ATMソフトウェアの観点から、控除の家計収支を伴う和出金取引は、お金を吐き出すが、新たな取引記録のシリーズ:私たちは見つける生活の中でアトミック操作の例としては、人々がATM機から現金を引き出すことができるということです操作が、ユーザの操作の観点からATMです。この操作は、どちらかである私たちは現金を得る成功は、(口座残高が差し引かれます)この操作は、後に行われます。この操作は決して起こらなかったように(口座残高がないように、私たちは現金を取得していないこと、または失敗します)差し引かれます。ATMソフトウェアに欠陥がある場合を除き、我々は口からキャッシュディスペンサーのいくつかは発生しませんし、私たちの口座残高は、この部分的な結果から控除されました。
  一般的には、Javaで、アトミック性を達成するための2つの方法があります。一つは、ロック(LOCK)を使用することです。それは共有変数を保護することができ、排他的ロックが一つだけのスレッドがいつでもアクセスすることが可能です。これは、複数のスレッドの可能性は、競合との干渉に同時にリード、レースのつまり除去で同じ共有変数にアクセス不可能にします。もう一つは、CAS(比較交換)命令を提供するために、専用プロセッサを使用することです。CAS命令アトミックロック方法の不可分は、実質的に同じ方法、差は、ロックは、通常、ソフトウェアレベルで実現されることで、ハードウェアにCAS(プロセッサおよびメモリ)を直接このレベルを達成しますそれは次のように見ることができる「ドングル。」
  Java言語では、long型とdouble型以外の任意のタイプの書き込み動作変数は、書き込み操作がアトミックである(二重以外長い)原子、即ち、参照変数とベースの変数型です。この点は、Java仮想マシンによって具体Java言語仕様(Java言語仕様)で定義されます。32ビットのJava仮想マシンで長い/二重可変読み取り/書き込み操作は、長いスレッドに導かれ、実装されている(ロー32、ハイライト32を書き込むためにこのような第1など)は、2つのサブステップに分けることができます/他のスレッドが観察されるdouble型変数の書き込み動作の中間結果が、すなわち、アクセス動作は、ダブルタイプの原子ロング/変数の場合ではありません。それにも関わらず、書き込み動作の揮発性のキーワードに与えられた特定のJava言語仕様は、アトミック変数ロング/ double型を変更しました。したがって、我々は唯一の揮発性のキーワードを必要とするダブルlong型の変数を変更(次の記事は、さらにキーワードを紹介します)/複数のスレッドによってアクセスすることができる、あなたは変数のアトミック書き込み動作を保証することができます。

III。可視性

  共有変数のスレッドが更新された後、マルチスレッド環境では、フォローアップの変数スレッドへの訪問をすぐにでも更新された結果を読んだことがない、更新された結果を読み取ることができない場合があります。可視性:これは表現の別の形でスレッド安全性の問題です。
  視界の次の例を参照してください。

// Code 2-2
public class VisibilityDemo {
    public static void main(String[] args) {
        UselessThread uselessThread = new UselessThread();
        uselessThread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        uselessThread.cancel();
    }
}

class UselessThread extends Thread {
    private boolean cancelled = false;

    @Override
    public void run() {
        System.out.println("Task has been started.");
        while (!cancelled) {}
        System.out.println("Task has been cancelled.");
    }

    public void cancel() {
        cancelled = true;
    }
}

  スレッド意志出力をその時点で上記のプログラム、uselessThreadスレッドでメインスレッドが開始されるが、「タスクが開始されました。」、1秒後に、メインスレッドはすなわち、キャンセル方法uselessThreadを呼び出す、calcelled変数のuselessThreadはtrueに設定されています。理論的には、runメソッドuselessThreadで、この時間は、ループが終了しますが、出力した後、「タスクがキャンセルされました。」スレッドの終了を。しかし、プログラムを実行するために、我々は次のような出力が表示されます。

Task has been started.

  私たちは、プログラムが出力しないことがわかった「タスクがキャンセルされました。」、(このような現象は、あなたは、javaコマンドの後に、-serverオプションを追加することができます表示されない場合)プログラムがまだ実行されています。この現象は、無限ループに陥っている間方法を実行している唯一の説明です。言い換えれば、変数のサブスレッドuselessThreadリードの値をキャンセルするメインスレッドはこの変数の値がtrueで更新しているにもかかわらず、常にfalseです。それは、ここで見メインスレッドは目に見えないキャンセルサブスレッドuselessThread共有変数を更新し、視認性の問題を抱えていましたさ。
  上記の例では、視認性の問題コードがJITコンパイラに十分ではないので、それが唯一のスレッドが取り消さ読取状態コードを改善するために、繰り返しを避けるために、可変JITコンパイラで得、それをアクセスすることを解除した状態変数ようヒント動作効率、およびwhileループは、次のコードと方法とマシンコード等価を実行するように最適化されています。

if (!cancelled) {
    while (true) {}
}

  残念ながら、現時点では、我々はプログラムを見てきている無限ループ、この最適化の結果が出ずに実行されています。
  一方、コンピュータやストレージシステムに関連する問題の可視性。プロセス変数は、レジスタの代わりに格納するためのメインメモリに割り当てられてもよいです。各プロセッサには独自のレジスタを持ち、プロセッサは別のプロセッサ上のレジスタの内容を読み取ることができません。したがって、2つのスレッドが異なるプロセッサ上で実行する、しかし、2つのスレッドで共有されている場合、レジスタに格納された変数に代入し、その可視性の問題を有することになります。さらに、場合でも、共有変数は、ストレージ用のメインメモリに割り当てられており、それは変数の可視性を保証することはできません。メインメモリへのプロセッサアクセスが直接アクセスするのではなく、そのキャッシュ・サブシステムを通じてされていないためです。キャッシュ・サブシステムの内容が更新されない場合、プロセッサは値がまだまた、視認性の問題を引き起こす可能性があり、古い価値があるかもしれないです読み込みます。

プロセッサは、メインメモリに直接対処し、メモリを読み出し実行し、しかし、レジスタ、キャッシュ、読み取り - 書き込みメモリバッファおよび無効化部材、書き込み動作を実行するキューを定義することにより、書き込み動作しません。このような観点から、これらのコンポーネントは、メインメモリコピーに対応するので、便宜上、本これらの成分をまとめてプロセッサ・キャッシュと呼ばれるプロセッサキャッシュのメインメモリと呼ばれます。

  別のプロセッサへの1つのプロセッサのキャッシュの内容を直接読み取ることはできないが、プロセッサは、キャッシュ・コヒーレンシ・プロトコル(キャッシュコヒーレンスプロトコル)によって他のプロセッサのキャッシュ内のデータを読み出すことができるが、そしてプロセッサのキャッシュに読み出したデータを更新します。そのようなプロセッサは、プロセッサ自体以外の記憶部から読み出して、我々はキャッシュの同期化を呼び出す処理プロセスのキャッシュにデータキャッシュを更新し、記憶手段は、プロセッサのキャッシュを含みます、メインメモリ。キャッシュの同期がプロセッサ上で実行中のスレッドが行う共有変数に別のプロセッサのアップデートで実行中のスレッドを読むことができます、可視性を保護することです。そのため、可視性を保証するために、我々は最終的には(しかし、常に自分の書き込みバッファ内に滞在しない)プロセッサのキャッシュやメインメモリに書き込まれ、共有変数に行われたプロセッサの更新を行う必要があり、工程プロセッサのキャッシュをフラッシュすると呼ばれます。この前に他のプロセッサは、変数を更新した場合と、プロセッサは、共有変数を読み込みます。そして、プロセッサキャッシュはそれぞれの変数他のプロセッサのキャッシュまたはメインメモリから同期させる必要があります。このプロセスは、リフレッシュプロセッサキャッシュと呼ばれています。そのため、セキュリティの可視性は、プロセッサキャッシュによって実行されるリフレッシュプロセッサは、アクションを洗い流す共有変数実行プロセッサのキャッシュを更新し、共有変数を達成するためにプロセッサの動作をお読みください。
  だから、Javaプラットフォームでどのように我々はそれの視認性を確保できますか?実際には、volatileキーワードの使用は、視認性を確保することができます。コード2-2に示すコードについては、我々は唯一でキャンセルにインスタンス変数を宣言するために揮発性のキーワードを追加する必要があります。

private volatile boolean cancelled = false;

  ここでは、そのJITコンパイラを示唆し、揮発性のキーワードで演奏される役割が変更された変数は、望ましくない操作の最適化を引き起こす可能性があり、プログラムを作るためにJITコンパイラを防ぐために、複数のスレッドで共有することができます。もう一つの重要な役割は、揮発性の変数を読み込むように変更され、プロセッサは、プロセッサのキャッシュを更新するために適切なアクションを実行させる揮発性のキーワード修正変数を書きますので、プロセッサは適切なアクションプロセッサのキャッシュフラッシュを実行するようになります可視性の保護。
  同じ目的のために共有変数は、スレッドが変数の値を更新した後、他のスレッドは、この更新された値を読み取ることができ、この値は、新しい変数の相対値と呼ばれています。他のスレッドが変数の値を更新することができないときは、このスレッドを読み取り、その変数を使用する共有変数を読み込む場合、そのスレッドは、変数の最新の値と呼ばれる相対値を読み取ります。可視性の保護は、単にスレッドが比較的新しい共有変数の値に読み取ることができることを意味しますが、スレッドは、対応する変数に最新の値を読み取ることができることを保証することはできません。
  アトミックの場合は、Java言語仕様では、2つのスレッドの開始を定義し、関連する仕様を停止します。

  1. スレッドの前に親スレッドが共有変数のプロモーターは、スレッドがある子に表示され更新されます。
  2. このスレッドポストのメソッドを呼び出すに参加するために、共有変数へのスレッドの更新プログラムのいずれかのスレッドスレッドは終了に見えます。

IV。秩序

  注文は、メモリアクセス動作の他のスレッドが別のプロセッサ上で実行することにより、実行いくつかの例プロセッサ上とで実行中のスレッドは順不同であるように思われることを意味します。いわゆる障害、シーケンシャルメモリアクセス動作に変更されているように見える指します。この概念の詳細を注文する前に、我々は、並べ替えの概念を導入する必要があります。

並べ替えコンセプト

  シーケンシャル構造は、それは我々が別の操作に先行しなければならないアクションが実行されたいということを意味し、プログラミングの基本的な構造です。さらに、2つの動作が順次任意を使用して実行することができるが、これらの二つの操作のためのコードに反映されていても常に優先関係を有します。しかし、マルチコアプロセッサの環境において、このような操作を行うことができる順番は保証されない:コンパイラは、2つの動作の順序を変更することができる、プロセッサ命令が完全にオブジェクトコードプログラムによって指定された順序で実行されなくてもよい。加えて、複数のプロセッサ上で実行される操作は、順序が他のプロセッサの観点からオブジェクトコードによって指定された順序と一致しないかもしれません。この現象は、並べ替えと呼ばれています。
  並べ替えは、メモリアクセス動作に関連している(読み取りおよび書き込み)シングルスレッドプログラムの正しさに影響を与えることなく、プログラムのパフォーマンスを向上させることができ、最適化を行いました。しかし、それは、マルチスレッドプログラムの精度に影響を与える可能性があることである、それはスレッド安全性の問題につながる可能性があります。そして、可視性の問題と同様の並べ替えが必ずしも発生していません。
  (基本的にJITコンパイラを意味するJavaプラットフォームで)コンパイラ、(ライトバッファ・キャッシュを含む)プロセッサおよびメモリ・サブシステムを含む多くの並び替え電位源。以下の説明を容易にするために、我々は最初のシーケンシャルメモリ操作関連用語の数を定義します。

  • シーケンスソース:ソースコード内で指定されたアクセス・メモリ一連の動作。
  • プログラムシーケンス:指定された特定のプロセッサ上で実行されているターゲットコードメモリアクセス動作シーケンス。
  • 実行順序は:実際のメモリアクセス動作は、所定のプロセッサで順次行います。
  • 認識配列:プロセッサによって知覚されるプロセッサ・メモリ・アクセスが発生すると、他のプロセッサの動作の間の所与の配列。

  次の表に示すように、これに基づき、我々は、並べ替え、並べ替え命令の並べ替えを分割、およびストレージ・サブシステムの2種類:

命令の並べ替え

  ソース・コード・シーケンスとシーケンスプログラムで矛盾している、または実行順序のプログラム順序と一致しない、我々は、命令が発生すると言われて並べ替えます。操作は順次命令の実際にフィールドが調整されている命令で並べ替え、それは並べ替え命令の対象です。

静的コンパイラ(javacの)及び動的コンパイラ(JITコンパイラ):Javaプラットフォームは、2つのコンパイラを含みます。かつての役割は、コードのコンパイルフェーズに関与している(.classファイルのバイナリファイル)バイトコードにコンパイルされたJavaソースコード(.javaファイル、テキストファイル)を、です。後者の役割は、動的にJavaプログラムを実行するプロセスに関与しているJava仮想マシンのホストネイティブコード(マシンコード)、のためのバイトコードにコンパイルされます。

  次のプログラムを考えてみます。

// Code 2-3
public class PossibleReordering {
    private static int a;
    private static int b;
    private static int x;
    private static int y;

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            a = 1;
            x = b;
        });
        Thread threadB = new Thread(() -> {
            b = 1;
            y = a;
        });
        threadA.start();
        threadB.start();
        try {
            threadA.join();
            threadB.join();
            System.out.printf("(%d,%d)", x, y);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  スレッドは、スレッドBの開始前に完了することに起因する実行可能スレッドAが開始する前に、スレッドBも完了することができる、二人はまた、プログラムは、最終的に出力が不確定であるなるか、従って、交互に行うことができます。しかし、我々の知識によれば、各スレッドで動作して実行されるコードの順序でなければなりません。すなわち、A = 1が実行されなければならない、される前にX = B、B = Yは=前に1が実行されなければなりません。私たちは、最終的な出力を分析するためにこれらのいくつかの簡単な操作を手配することができます。

アクション1 操作2 オペレーション3 操作4 結果
= 1 X = B B = 1 Y = A (0,1)
= 1 B = 1 X = B Y = A (1,1)
= 1 B = 1 Y = A X = B (1,1)
B = 1 Y = A = 1 X = B (1,0)
B = 1 = 1 Y = A X = B (1,1)
B = 1 = 1 X = B Y = A (1,1)

  これは、プログラムの出力は、(1,0)、(0,1)または(1,1)が可能であり、適切な同期の非存在下で見ることができます。しかし、不思議なこと、プログラムはまた、出力(0,0)、この結果は、上記の分析のいずれの場合にも属していないことがあります。上記の各動作における各スレッドのデータストリームの間に依存関係がないので、命令の並べ替えが発生する可能性があり、したがって、これらの操作順序を外れが実行されてもよいです。次の図は、出力は(0,0)になり、その場合、並べ替えによって生じる交互の可能な実装を示します。

  並べ替えはスレッド安全性の問題につながる可能性があり、見ることができます。もちろん、これは自分自身の順序を変更することは間違っているというわけではありませんが、私たちのプログラムに問題があります。私たちのプログラムは、使用しないか、適切にスレッド同期メカニズムを使用していません。しかし、並べ替えは避けられないが、(0,0)上記のは、おそらく一度だけ登場約50,000回実行するプログラムです。それにもかかわらず、我々は、並べ替えによってもたらされる潜在的なリスクを無視することはできません。
  他のコンパイル言語(例えば、C ++)では、命令が再順序付けコンパイラを引き起こす可能性があります。Javaプラットフォームでは、静的コンパイラ(javacは)基本的に、命令の並べ替えを行わず、JITコンパイラは、命令の並べ替えを実行してもよいです。
  プロセッサは、実行順序がプログラム順序と一致しないことができる命令の並べ替えを実行してもよいです。オーダー実行のうちプロセッサ命令は、次の命令に必要なデータの取得によって引き起こさ待っ避け、すぐに後続の命令を実行するための直流ランニング能力を可能にする条件下でプロセッサを並べ替えると呼ばれます。オーダー実行技術により、大幅プロセッサの効率を向上させることができます。プロセッサを並べ替え命令は、シングルスレッドのプログラムの正しさに影響を与えませんが、マルチスレッド・プログラムを表示され、それは、予期しない結果につながることができます。

ストレージ・サブシステムの並べ替え

  プロセッサに対するメインメモリ(RAM)が遅いデバイスです。ドラッグを避けるために、プロセッサは、メインメモリへのアクセスが、キャッシュアクセスのメインメモリを介して直接ではありません。これに基づいて、最新のプロセッサの効率は、書き込みキャッシュ操作を改善するために書き込みバッファを導入しました。メインメモリ書き込み動作の全てについて(例えば、Intelのx86プロセッサのような)いくつかのプロセッサは、書き込みバッファにより行われます。ここで、我々は総称してライトバッファとキャッシュ・メモリ・サブシステムになる、それは実際には、サブプロセッサです。
  図9に示すように、これら二つの動作の他のプロセッサは、依然としてプロセッサは厳密にストレージ・サブシステムおよびプログラム順序の影響の下で、プログラム順に2つのメモリアクセス動作を強制する場合であっても、これらの二つの操作ようにするためには、一貫性がない可能性が認識されています実行順序が変更されているように見えます。この現象は、メモリの並べ替えとして知られているストレージ・サブシステムの並べ替え、です。
  ストレージ・サブシステムが並べ替え操作ではなく、現象である一方で、それは実際の命令シーケンス全体の、乾燥しているされた並べ替えコマンドオブジェクトの順序を変更するコマンドであり、それは本当に、順次命令の実行のために調整されませんむしろ、命令の実行順序の結果は、オブジェクトがメモリ操作の並べ替えの結果であり、このような現象のように調整されます。
  プロセッサの観点から、メモリ動作が読み出しメモリ操作は、一般に負荷と呼ばれるように、レジスタに(ロードされたキャッシュを介して)指定されたRAMアドレスデータの本質からロードすることで読み取り、実質的なメモリ書き込み動作は、データを記憶することです指定されたアドレスで示されるRAMメモリユニット、そうメモリ書き込み動作は、一般的にストアと呼ばれます。だから、実際には、唯一、次の4つの可能性の並べ替えメモリ:

  メモリの並べ替えは、スレッド安全性の問題が発生することがあります。それぞれ下に示すインターリーブされた順番に二つのスレッドによるプロセッサプロセッサプロセッサ0、プロセッサ1は、データレディ2つのスレッドが変数、0および偽の初期値を共有する、そのコードを実行すると仮定する。プロセッサ0の処理論理スレッドは、データDATAと真の準備それぞれの更新フラグのその後の値を更新するために実行されます。フラグの値が真のデータに更新されたときに1つのプロセッサが実行される論理スレッドを処理するデータが真である前に準備を無期限に待機する準備ができていません
値がプリントアウト。

  プロセッサ0が順次プログラム順実行S1とS2において想定される、動作はS1とS2を順次書き込みバッファに書き込まれる結果。しかしながら、特定のプロセッサに保証せず、書き込みキャッシュの内容、すなわちレイトライトバッファは、以前の結果があってもよい書き込み動作に達し、最初に順に第一の演算結果を記述した効率を向上させるためにライトバッファキャッシュを書き込むので、ステップS2は、次いで、並べ替え(リオーダリングメモリ)S2にS1がキャッシュに書き込まれた演算結果、即ち、S1に先行してもよいです。操作S1の結果がまだ書き込みバッファプロセッサ0、及びプロセッサで立ち往生されており、他のプロセッサに読み取ることができないので、読んで準備ができて1つのプロセッサ上のスレッドは、真であるとき、これはつながりますバッファの内容を書き込むので、データ値を読み出すための1つのプロセッサ上でスレッドが依然として0です。目に見える、この時間は、スレッド安全性の問題につながったその意図した目標を達成することはできません1つのプロセッサに並び替えメモリ処理ロジックスレッドにつながりました。

シーケンシャルメモリアクセスを確認してください

  それによって引き起こされたスレッドの安全性の問題を並べ替え避けるためにどのように?私たちは、プロセッサが完全なソースコードシーケンスに従って命令を実行することを、我々は物理的にパフォーマンス限り低く、並べ替えなどを無効にすることができないことを知っておく必要があります。しかし、我々は選択の並べ替えが発生しないか、それは論理的にマルチスレッドプログラムの発生の有効性に影響を与えない場合でもどちらかであることを並べ替えを禁止することができます。
  底の観点から並べ替えは、それぞれ(メモリバリア)を提供するコマンドプロセッサを呼び出すことによって達成される禁止します。もちろん、クロスプラットフォームの言語としてのJavaは、それが私たちのためにこのような命令に対処します、と私たちは言語そのものによって提供されるメカニズムを使用する必要があります。我々は前述の揮発性のキーワード、synchronizedキーワードは秩序を達成することができます。volatileキーワード、関連キーワードsynchronizedと並べ替えが、私たちは、その後の記事では、より深い理解を行います。

おすすめ

転載: www.cnblogs.com/maconn/p/11489606.html