あなたが尋ねている本質的に2つの質問があります:
1.並べ替えによってgetInstance()
メソッドが戻るnull
ことはありますか?
(これはあなたが本当に求めているものだと思うので、最初に答えようとします)
これを許可するようにJavaを設計することは完全に狂っていると思いますがgetInstance()
、 nullを返すことができるのは実際には正しいようです。
あなたのコード例:
if (resource == null)
resource = new Resource(); // unsafe publication
return resource;
リンク先のブログ投稿の例と論理的に 100% 同一です。
if (hash == 0) {
// calculate local variable h to be non-zero
hash = h;
}
return hash;
Jeremy Manson は、並べ替えによってコードが 0 を返す可能性があると説明しています。最初は、次の「前に起こる」ロジックが成り立つはずだと思っていたので、信じられませんでした。
"if (resource == null)" happens before "resource = new Resource();"
and
"resource = new Resource();" happens before "return resource;"
therefore
"if (resource == null)" happens before "return resource;", preventing null
しかし、Jeremy は彼のブログ投稿へのコメントで次の例を示しています。このコードがコンパイラによって有効に書き換えられる方法:
read = resource;
if (resource==null)
read = resource = new Resource();
return read;
これは、シングルスレッド環境では元のコードとまったく同じように動作しますが、マルチスレッド環境では次の実行順序になる可能性があります。
Thread 1 Thread 2
------------------------------- -------------------------------------------------
read = resource; // null
read = resource; // null
if (resource==null) // true
read = resource = new Resource(); // non-null
return read; // non-null
if (resource==null) // FALSE!!!
return read; // NULL!!!
さて、最適化の観点から、これを行うことは私には意味がありません.代わりに生成if (read==null)
して、問題を防ぎます。したがって、Jeremy が彼のブログで指摘しているように、おそらくこれが起こる可能性はほとんどありません。しかし、純粋に言語規則の観点から見ると、実際には許可されているようです。
この例は、実際には JLS でカバーされています。
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4
r2
、r4
、およびr5
inの値の間で観察される効果は、上記の例の、、および でTable 17.4. Surprising results caused by forward substitution
発生する可能性があるものと同等です。read = resource
if (resource==null)
return resource
余談ですが、回答の最終的な情報源としてブログ投稿を参照するのはなぜですか? それを書いた人は、並行性に関する JLS の第 17 章を書いた人でもあるからです! だから、彼は正しいほうがいいです!:)
2.Resource
不変にすると、getInstance()
メソッドはスレッドセーフになりますか?
ミュータブルかどうかに関係なく発生する可能性のある潜在的なnull
結果を考えると、Resource
この質問に対する即時の簡単な答えは次のとおりです。いいえ(厳密ではありません)
ただし、この非常にありそうもないが可能性のあるシナリオを無視すると、答えは次のようになります。
コードの明らかなスレッドの問題は、次の実行順序につながる可能性があることです (並べ替えの必要はありません)。
Thread 1 Thread 2
---------------------------------------- ----------------------------------------
if (resource==null) // true;
if (resource==null) // true
resource=new Resource(); // object 1
return resource; // object 1
resource=new Resource(); // object 2
return resource; // object 2
したがって、非スレッド セーフは、関数から 2 つの異なるオブジェクトが返される可能性があるという事実から来ています (ただし、順序を変更しなければどちらもnull
.
さて、この本がおそらく言おうとしていたことは、次のことです。
String や Integers などの Java 不変オブジェクトは、同じコンテンツに対して複数のオブジェクトを作成しないようにします。したがって、"hello"
ある場所と"hello"
別の場所にある場合、Java はまったく同じオブジェクト参照を提供します。同様に、new Integer(5)
ある場所とnew Integer(5)
別の場所にある場合。これがnew Resource()
同様に当てはまる場合、同じ参照が返されobject 1
、object 2
上記の例ではまったく同じオブジェクトになります。これは実際、効果的にスレッドセーフな関数につながります (並べ替えの問題は無視されます)。
しかし、Resource
自分で実装する場合、コンストラクターが新しいオブジェクトを作成するのではなく、以前に作成されたオブジェクトへの参照を返すようにする方法さえないと思います。object 1
したがって、object 2
まったく同じオブジェクトを作成することはできません。しかし、同じ引数 (どちらの場合もなし) でコンストラクターを呼び出していることを考えると、作成されたオブジェクトがまったく同じオブジェクトでなくても、すべての意図と目的のために、次のように動作する可能性があります。もしそうなら、コードを効果的にスレッドセーフにすることもできます。
ただし、必ずしもそうである必要はありません。Date
たとえば、の不変バージョンを想像してみてください。デフォルトのコンストラクターDate()
は、現在のシステム時刻を日付の値として使用します。したがって、オブジェクトが不変であり、コンストラクターが同じ引数で呼び出されたとしても、それを 2 回呼び出しても、おそらく同等のオブジェクトにはなりません。したがって、このgetInstance()
メソッドはスレッドセーフではありません。
したがって、一般的な声明として、あなたが本から引用した行はまったく間違っていると思います(少なくともここでは文脈から切り離されているため)。
ADDITION Re: 並べ替え
このresource==new Resource()
例は少し単純すぎて、Java によるこのような並べ替えを許可することが理にかなっている理由を理解するのに役立ちません。それでは、これが実際に最適化に役立つ何かを考え出すことができるかどうか見てみましょう:
System.out.println("Found contact:");
System.out.println(firstname + " " + lastname);
if (firstname==null) firstname = "";
if (lastname ==null) lastname = "";
return firstname + " " + lastname;
ifs
ここで、両方がyieldになる可能性が最も高いケースでfalse
は、高価な String 連結firstname + " " + lastname
を 2 回 (デバッグ メッセージ用に 1 回、リターン用に 1 回) 行うのは最適ではありません。したがって、代わりに次のことを行うようにコードを並べ替えることは、ここでは理にかなっています。
System.out.println("Found contact:");
String contact = firstname + " " + lastname;
System.out.println(contact);
if ((firstname==null) || (lastname==null)) {
if (firstname==null) firstname = "";
if (lastname ==null) lastname = "";
contact = firstname + " " + lastname;
}
return contact;
例がより複雑になり、コンパイラーが使用するプロセッサーレジスターに既にロード/計算されているものを追跡し、既存の結果の再計算をインテリジェントにスキップすることについて考え始めると、この効果は実際にはますます可能性が高くなります。起こる。ですから、昨夜寝たときにこれを言うとは思っていませんでしたが、もっと考えてみると、コードの最適化が最大限に機能するようにするために、これは必要な/良い決定だったのではないかと実際に信じています。印象的な魔法。しかし、多くの人がこれに気づいていないと思うので、それはまだ非常に危険だと思います。
この並べ替えを許可しないと、一連のプロセス ステップの中間結果のキャッシュと再利用が違法になり、可能な限り最も強力なコンパイラ最適化の 1 つがなくなります。