5

このWeb サイトから直接、オブジェクト スレッド セーフの作成に関する次の説明に出くわしました。

警告: スレッド間で共有されるオブジェクトを構築するときは、オブジェクトへの参照が時期尚早に「リーク」しないように十分注意してください。たとえば、クラスのすべてのインスタンスを含むインスタンスという名前のリストを維持したいとします。次の行をコンストラクターに追加したくなるかもしれません。

インスタンス.追加(これ);

ただし、オブジェクトの構築が完了する前に、他のスレッドがインスタンスを使用してオブジェクトにアクセスできます。

同じ概念を他の言葉やもっと分かりやすい例で表現できる人はいますか?

前もって感謝します。

4

8 に答える 8

2

スレッド A はオブジェクト A を作成しています。作成オブジェクト A の途中 (オブジェクト A のコンストラクターの最初の行) で、コンテキスト スイッチがあります。これでスレッド B が機能し、スレッド B はオブジェクト A を調べることができます (彼は既に参照を持っていました)。ただし、スレッド A にはそれを完了する時間がないため、オブジェクト A はまだ完全には構築されていません。

于 2013-05-23T15:33:14.083 に答える
2

これがあなたの明確な例です:

という名前のクラスがあるとしましょうHouse

class House {
    private static List<House> listOfHouse;
    private name;
    // other properties

    public House(){
        listOfHouse.add(this);
        this.name = "dummy house";
        //do other things
    }

 // other methods

}

そして村:

class Village {

    public static void printsHouses(){
         for(House house : House.getListOfHouse()){
               System.out.println(house.getName());
         }
    }
}

スレッドでを作成する場合はHouse、「X」. そして、実行中のスレッドが次の行を終了したとき、

listOfHouse.add(this); 

そして、コンテキストがlistOfHouse別のスレッド「Y」実行に切り替えられます(オブジェクトの作成はまだ終了していませんが、このオブジェクトの参照はすでにリストに追加されています)。

printsHouses();

初期化!その後printHouses()、まだ完全に作成されていないオブジェクトが表示されます。このタイプの不一致は として知られていLeakます。

于 2013-05-23T15:48:46.743 に答える
2

ここには多くの良いデータがありますが、さらに情報を追加したいと思いました。

スレッド間で共有されるオブジェクトを構築するときは、オブジェクトへの参照が時期尚早に「リーク」しないように十分注意してください。

オブジェクトを作成している間は、完全に作成する前に他のスレッドがこのオブジェクトにアクセスできないようにする必要があります。つまり、コンストラクターでは、次のようにすべきではありません。

  • static他のスレッドからアクセスできるクラスのフィールドにオブジェクトを割り当てます。
  • 完全に初期化される前に、オブジェクトのフィールドの使用を開始する可能性があるコンストラクターでオブジェクトのスレッドを開始します。
  • オブジェクトをコレクションにパブリッシュするか、他のスレッドがオブジェクトを完全に構築する前に参照できるようにするその他のメカニズムを介してパブリッシュします。

次の行をコンストラクターに追加したくなるかもしれません。

   instances.add(this);

したがって、次のようなものは不適切です。

  public class Foo {
      // multiple threads can use this
      public static List<Foo> instances = new ArrayList<Foo>();
      public Foo() {
         ...
         // this "leaks" this, publishing it to other threads
         instances.add(this);
         ...
         // other initialization stuff
      }
      ...

さらに複雑なのは、Java コンパイラー/オプティマイザーには、コンストラクター内の命令を並べ替えて、後で実行できるようにする機能があることです。これはinstances.add(this);、コンストラクターの最後の行として実行しても、コンストラクターが実際に終了したことを確認するには不十分であることを意味します。

複数のスレッドがこのパブリッシュされたオブジェクトにアクセスする場合は、synchronized. 心配する必要のない唯一のフィールドはfinal、コンストラクターが終了したときに構築が完了することが保証されているフィールドです。 volatileフィールド自体が同期されるため、心配する必要はありません。

于 2013-05-23T16:07:56.033 に答える
0

次の状況を説明しています。

Thread1:
 //we add a reference to this thread
 object.add(thread1Id,this);
 //we start to initialize this thread, but suppose before reaching the next line we switch threads
 this.initialize(); 
Thread2:     
//we are able to get th1, but its not initialized properly so its in an invalid state 
//and hence th1 is not valid
Object th1 = object.get(thread1Id); 
于 2013-05-23T15:38:02.663 に答える
0

スレッド スケジューラはいつでもスレッドの実行を停止し ( のような高レベルの命令の途中でもinstances.push_back(this))、別のスレッドの実行に切り替えることができるため、オブジェクトへの並列アクセスを同期しないと、予期しない動作が発生する可能性があります。

以下のコードを見てください。

#include <vector>
#include <thread>
#include <memory>
#include <iostream>

struct A {
    std::vector<A*> instances;
    A() { instances.push_back(this); }
    void printSize() { std::cout << instances.size() << std::endl; }
};

int main() {
    std::unique_ptr<A> a; // Initialized to nullptr.

    std::thread t1([&a] { a.reset(new A()); }); // Construct new A.
    std::thread t2([&a] { a->printSize(); }); // Use A. This will fail if t1 don't happen to finish before.

    t1.join();
    t2.join();
}

ain main()-functionへのアクセスは同期されていないため、実行は時々失敗します。

これt1は、オブジェクトの構築が完了する前にスレッドの実行が停止され、代わりにAスレッドt2が実行された場合に発生します。これにより、スレッドがを含むt2にアクセスしようとします。unique_ptr<A>nullptr

于 2013-05-23T16:33:26.240 に答える