String
内部的には、オフセットと長さを持つ文字配列です。この文字配列は、以前は複数の文字列間で再利用されていました。メモリ使用の最適化として、元の文字列と同じ文字配列に裏打ちされsubstring()
た新しいものを返します。String
このメソッドが行うことは、結果文字列のこの文字配列への新しいオフセットと長さを決定し、プライベート コンストラクターを呼び出してこの結果オブジェクトを作成し、それを返します。
(Joachim が指摘しているように、これはもはやString
機能する方法ではありませんが、JLS の例は古い内部構造に基づいています。)
さて、そのプライベート コンストラクターの実装、JIT または CPU による命令の並べ替え、または一般的にはスレッド間で共有されるメモリの奇抜な動作によって引き起こされる可能性があるのは、この新しいString
オブジェクトが最初にその長さを に設定すること4
です。オフセットは に留まり、この0
値を にしますString
"/tmp"
。わずか 1 秒後にオフセットが に設定され4
、その値が正しく になります"/usr"
。
別の言い方をすれば、スレッド 2は、コンストラクターの実行中にこの文字列を観察できます。これは直観に反しているように見えます。なぜなら、人々はスレッド 1のコードを、最初に代入の右側を完全に実行し、その後で の値を変更するだけであると直感的に理解しているからですGlobal.s
。残念ながら、適切なメモリ同期がなければ、他のスレッドは異なる一連のイベントを観察できます。フィールドを使用final
することは、JVM にこれを正しく処理させる 1 つの方法です。( Global.s
as宣言volatile
も機能すると思いますが、100% 確実ではありません。)