2017-05-07 21 views
0

スレッドセーフであるかどうかを確認しようとしています。私はそうだと信じていますが、最近誰かがこのメソッドのスレッドの安全性を疑問視しました。スレッドの安全性を理解する

のは、私は私たちに次のインタフェースを実装するクラス与えるいくつかの工場FactoryAあるとしましょう:だから

public abstract class MyFactory { 
    private ObjectA object; 
    public void init(ObjectA object){ 
     _object = object; 
    } 
} 

を、私たちは今、

public class FactoryA extends MyFactory { 
    static final createFactoryClass(String name) { 
     // ...ignorning safety checks for brevity 
     return MY_MAP.get(name).newInstance(); 
    } 
} 

のように、私は別で、いくつかの方法を持っている何かを持っていますファクトリーを取り上げ、可能なクラスの地図を返すクラス:

public class OtherClass { 
    private static FactoryA _factory = new FactoryA(); 
    private static final Map<String, SomeClass> MY_MAP = new ImmutableMap.Builder<String, MyClass>() 
    .put("foo", _factory.createFactoryClass("foo")); 

    private SomeObject myMethod(ObjectA objectA, SomeObject someObject) { 
     MY_MAP.get(someObject.getString()).init(objectA); 
    } 
} 

質問はinitメソッドがスレッドセーフであるかどうかです。マップは1回しか初期化されないため、2つのスレッドが異なるObjectAを呼び出すと、不変構造に格納されていても間違ったクラスが間違って使用される可能性がありますObjectA

次のようにしてこの問題を解決できますか?変数、すなわち、次の例のように、共有されるオブジェクトへの参照でない限り

private static synchronized myMethod(...) {}

+0

コード内では、クロスクラスと型の不一致を参照しているので、少し混乱します。しかし、私は本当の疑問はあなたの 'init'メソッドでやっていることにあると思いますか? – Xerillio

+1

あなたの 'MyFactory.init'メソッドは、何もしないので完全にスレッドセーフです。 –

+0

'createFactoryClass()'もスレッドセーフであるようです。地図を一度初期化したら、もう一度書き込んではいけません。ここでの条件は、まだ見えるようにしなければならないということです。それを 'final'に割り当てることは罰金であり、' volatile'と 'synchronized'のようにマップ(そしてすべての書き込み)を見えるようにします。 C.f。 ["安全な出版物"](http://stackoverflow.com/questions/801993/java-multi-threading-safe-publication) – markspace

答えて

1

ローカル変数は、常にスレッドセーフである:

static void setValue(Example obj, int val) { 
    obj.val = val; 
} 

Example sharedObj = ...; 

new Thread(() -> { setValue(sharedObj, 1); }).start(); 
new Thread(() -> { setValue(sharedObj, 2); }).start(); 

その例では、しかし、安全ではないsharedObj自体への参照を使用するのではなく、その参照を使用してsharedObj.valの状態を同時に変更しています。

代わりに、我々は、異なるオブジェクトへの参照を持つ2つのスレッドを持っていた場合は、次のJVMが混乱し、例えば渡しません

Thread threadA = new Thread(() -> { 
    Example objA = ...; 
    setValue(objA, 1); 
}); 
Thread threadB = new Thread(() -> { 
    Example objB = ...; 
    setValue(objB, 2); 
}); 
threadA.start(); 
threadB.start(); 

threadAのオブジェクトthreadBの呼び出しはsetValueです。逆も同様です。それは、あなたが聞いているように聞こえるようなものです。それは起こらないでしょう。すべてのスレッドには独自の呼び出しスタックがあり、各スレッドの呼び出しであるsetValueは独自のスレッドで新しいスタックフレームを開きます。つまり、threadAsetValueを呼び出し、threadAに独自のローカル変数を設定し、threadBsetValueを呼び出して、threadBに独自のローカル変数を持つスタックフレームを作成します。

initメソッドの変更が他のスレッドから見えないことがあるという懸念があります。たとえば、オブジェクトがinitに渡すことによってオブジェクトを初期化し、次にそれらのオブジェクトを他のThread thread2に渡すことであるThread thread1があるとします。 thread2は、thread1のオブジェクトに行われたinitの変更を表示しますか?答えはです。何らかの種類のメモリ同期がありません。thread1initの呼び出し中に加えられた変更がthread2に表示されない場合があります。

myMethodを同期させても、おそらくこの問題は解決しません。代わりに、thread1thread2は、共有するモニターオブジェクトやロックのように、通信するための何らかの方法を必要とします。

地図の初期化についてのヒントがあると思います。マップは、静的初期化子中にのみ構築されている場合はclass initialization is performed under synchronizationとするので、それは、スレッドセーフです:

実装はロック獲得をelidingことで、この手順を最適化することができる[...]それは決定することができたときにクラスの初期化は既に完了しています。ただし、メモリモデルの点では、ロックが取得された場合に存在するすべての順序が最適化が実行された時点で存在していなければなりません。言い換えれば

、静的初期化はJVMが特定のクラスが初期化されているので、それがしばらくの間をされていると判断し、初期設定を取得しようとせずにフィールドを読み取ることが決定した場合でも、他のスレッドで見られることが保証されていますロック。

-1

現在、何が起こっているのかを理解することは難しいので、クラス/メソッドの名前を変えることをお勧めします。また、私は工場をシングルトンにします。

関連する問題