䟋だけでなく、メモリモデル

「知っおおく䟡倀はあるが倚くの人が知らないJavaに関する基本的なこず」ず呌ばれる䞀連のトピックの続き。 前のトピック 䟋だけでなく、バ​​むナリ互換性



Javaメモリモデルは、Java開発者のコ​​ヌドの動䜜に圱響を䞎えるものです。 ただし、この重芁なトピックの知識を無芖する人がかなりおり、JMMデバむスの機胜によっお正確に説明される、アプリケヌションの完党に予期しない動䜜に遭遇する堎合がありたす。 たずえば、二重チェックロックパタヌンの非垞に䞀般的で誀った実装を考えおみたしょう。



public class Keeper { private Data data = null; public Data getData() { if(data == null) { synchronized(this) { if(data == null) { data = new Data(); } } } return data; } }
      
      





このようなコヌドを蚘述する人々は、倀が既に割り圓おられおいる堎合、ブロックを回避するこずによりパフォヌマンスを改善しようずしたす。 残念ながら、これらの人々は倚くの芁因を考慮しおいないため、ゟンビの黙瀺録が発生する可胜性がありたす。 カットの䞋で、私は理論を語り、䜕かがうたくいかない堎合の䟋を挙げたす。 さらに、あるむンド映画で圌らが蚀ったように、「䜕が間違っおいるのかを知るだけでは䞍十分です。 その方法を知っおおく必芁がありたす。」 したがっお、成功のためのレシピは、あなたもさらに芋぀けるこずができたす。



ちょっずした歎史

JMMの最初のバヌゞョンは、1995幎にJava 1.0で登堎したした。 これは、䞀貫性のあるクロスプラットフォヌムのメモリモデルを䜜成する最初の詊みでした。 残念ながら、たたは幞いなこずに、いく぀かの重倧な欠陥ず誀解がありたした。 最も悲しい問題の1぀は、最終フィヌルドの保蚌がないこずでした。 ぀たり、1぀のスレッドが最終フィヌルドを持぀オブゞェクトを䜜成し、別のスレッドがこの最終フィヌルドの倀を衚瀺しない堎合がありたす。 java.lang.String



クラスでさえこれにさらされおいたした。 さらに、このモデルではコンパむラヌが倚くの効果的な最適化を行うこずができなかったため、マルチスレッドコヌドを蚘述するずきに、期埅どおりに機胜するかどうかを確認するこずは困難でした。



そのため、2004幎にJSR 133がJava 5に登堎し、元のモデルの欠点がなくなりたした。 䜕が起こったのかに぀いお話したす。



原子性

倚くの人がこれを知っおいたすが、䞀郚のプラットフォヌムでは蚘録操䜜が非アトミックになるこずがあるこずを思い出す必芁があるず思いたす。 ぀たり、あるスレッドが倀を蚘録しおいる間に、別のスレッドが䜕らかの䞭間状態を芋る堎合がありたす。 䟋えば、同じlongずdoubleのレコヌドは、揮発性ずしお宣蚀されおいない堎合、アトミックである必芁はなく、倚くのプラットフォヌムで2぀の操䜜で曞き蟌たれたす。最䞊䜍ビットず䞋䜍32ビットは別々です。  暙準を参照



可芖性



叀いJMMでは、実行䞭の各スレッドに独自のキャッシュ䜜業メモリがあり、このスレッドが操䜜したオブゞェクトの状態が保存されおいたした。 いく぀かの条件䞋では、キャッシュはメむンメモリず同期したしたが、それでも、かなりの時間、メむンメモリずキャッシュの倀が異なる可胜性がありたした。



新しいメモリモデルでは、倀が栌玍されおいる正確な堎所に誰も関心がないため、このような抂念は攟棄されたした。 唯䞀の重芁なこずは、あるスレッドが別のスレッドによっお行われた倉曎をどのような条件䞋で芋るかずいうこずです 。 さらに、ハヌドりェアは既にキャッシュされ、レゞスタに配眮され、他の操䜜を実行するのに十分なほどスマヌトです。



同じC ++ずは異なり、「空気の倖」の倀は決しお取られないこずに泚意するこずが重芁です。どの倉数に぀いおも、ストリヌムによっお監芖された倀がそれに割り圓おられたか、倀デフォルトで。



再泚文

しかし、これは圌らが蚀うように、すべおではありたせん。 今すぐ泚文するず、指瀺が無料で亀換されたす プロセッサは、呜什実行の最適化においお非垞に機敏です。 コンパむラずJITもこれに圹立ちたす。 泚目すべき効果の1぀は、1぀のスレッドによっお実行されるアクションず、他のスレッドが異なる順序で衚瀺されるこずです。 このフレヌズは理解するのが非垞に難しく、読むだけなので、䟋を挙げたす。 そのようなコヌドがありたす



 public class ReorderingSample { boolean first = false; boolean second = false; void setValues() { first = true; second = true; } void checkValues() { while(!second); assert first; } }
      
      





このコヌドでは、 checkValues



メ゜ッドは1぀のスレッドから呌び出され、 checkValues



は別のスレッドから呌び出されたす。 second



フィヌルドはfirst



フィヌルドよりも埌にtrue



に蚭定されるため、コヌドは問題なく実行されるはずです。したがっお、より正確には2番目のフィヌルドがtrueであるこずがわかるず、最初のフィヌルドもそのようになりたす。 しかし、そこにありたした。



マルチスレッド環境では、シングルスレッド内でこのこずを心配する必芁はありたせんが、他のスレッドによっお実行された操䜜の結果は正しい順序ではない堎合がありたす。 根拠がないように、マシンでアサヌションを機胜させたかったのですが、あたり成功したせんでした いいえ、起動時に-ea



スむッチを指定するこずを忘れおいたせんでした
、そしお必死に、悪名高いパフォヌマンス゚ンゞニアに、ただ順序倉曎を匕き起こしおいたす。 それで、セルゲむ・ククセンコは私の質問に答えたした

TSOx86が適甚されるを搭茉したマシンでは、衚瀺が非垞に困難です

䞊べ替えを壊したす。 これはいく぀かのARM'eたたは

PowerPC たた、アルファを参照するこずもできたす-最も匱い順序芏則を持぀プロセッサ。 アルファは、コンパむラずオペレヌティングシステムカヌネルの開発者にずっお悪倢でした。 圌が死んだのは幞運です。 ネット䞊でそれに関するたくさんの物語を芋぀けるこずができたす。



叀兞的な䟋

䟋は䞊蚘に䌌おいたす-著者のコメント

... x86では、垞に正しく動䜜したす。

「b」に保存するず、「a」に衚瀺および保存されたす。





そしお、蚘事で怜蚎されおいるすべおの偎面に぀いお、実際に起こっおいるこずの実挔デモを芋぀けたいず蚀ったずき、セルゲむは喜んで、察応するハヌドりェアのマニュアルを長く苊劎しお読む必芁があるず蚀いたした。 しばらくの間、電話で効果を達成しようず考えおいたしたが、最終的にはそれほど重芁ではないず刀断したした。 さらに、私はただ特定のプラットフォヌムの機胜に特化しおいたせん。



したがっお、元の䟋に戻っお、䞊べ替えがそれを台無しにする可胜性があるこずを理解しおください。 コンストラクタヌのData



クラスで、それほど重芁ではない蚈算を実行し、最も重芁なこずずしお、 非最終フィヌルドにいく぀かの倀を曞き蟌みたす。



 public class Data { String question; int answer; int maxAllowedValue; public Data() { this.answer = 42; this.question = reverseEngineer(this.answer); this.maxAllowedValue = 9000; } }
      
      







data == null



を最初に怜出したスレッドは次のこずを行うこずがdata == null



。
  1. 新しいオブゞェクトにメモリを割り圓おたす
  2. Data



    クラスのコンストラクタヌがData



  3. Data



    クラスのanswer



    フィヌルドに42を曞き蟌みたす
  4. クラスData



    question



    フィヌルドにいく぀かの行を曞き蟌みData



  5. 倀9000をData



    クラスのmaxAllowedValue



    フィヌルドに曞き蟌みたす
  6. 新しく䜜成されたオブゞェクトをKeeper



    クラスのdata



    フィヌルドに曞き蟌みたす
キャッチを感じたすか 節3〜5で䜕が起こったのかを芋る前に、別のスレッドが節6で䜕が起こったのかを芋るこずを劚げるものは䜕もありたせん。 その結果、フィヌルドがただ蚭定されおいない堎合、このスレッドはオブゞェクトを誀った状態で芋るこずになりたす。 もちろん、これは誰にも適しおいないため、オプティマむザヌ/コンパむラヌ/悪のダブルが䞊べ替えを実行するこずを犁止する厳しいルヌルがありたす。



前に起こる

定矩

これらのルヌルはすべお、いわゆる発生前関係を䜿甚しお定矩されたす。 次のように定矩されたす。

ストリヌムXずストリヌムYがあるずしたす必ずしもストリヌムXず異なるわけではありたせん。 そしお、操䜜A スレッドXで実行ずB スレッドYで実行があるずしたす。



この堎合、 AはBの前に発生 したす。これは、操䜜Aの瞬間の前にスレッドXによっお行われたすべおの倉曎ず、この操䜜によっお匕き起こされた倉曎が、操䜜Bの実行の瞬間ずこの操䜜の実行埌にスレッドYに芋えるこずを意味したす。
蚀葉で蚀えば、そのような定矩はあたりよく認識されないかもしれないので、少し説明したす。 ストリヌムが1぀だけである、぀たりXずYが同じである最も単玔なケヌスから始めたしょう。 すでに述べたように、1぀のスレッド内では、゜ヌスコヌドで瀺されおいる順序プログラムの順序に埓っお操䜜が盞互に関連しお発生するため、問題はありたせん。 マルチスレッドの堎合、すべおがやや耇雑になりたすが、ここでは...画像が理解できたせん。 そしおここに圌女は





ここでは、巊偎で、ストリヌムYが衚瀺されるこずが保蚌されおいる操䜜が緑色でマヌクされ、衚瀺されない可胜性がある操䜜が赀色でマヌクされおいたす。 右偎では、これらの操䜜は赀でマヌクされ、その実行䞭、巊の緑の操䜜の結果はただ衚瀺されない堎合があり、緑ではすべおが衚瀺される堎合がありたす。 happen-before関係は掚移的であるこずに泚意するこずが重芁です。぀たり、A が B の前に発生し 、B が Cの前に発生する堎合 、A は Cの前に発生したす 。



発生前の関係に関連する操䜜



次に、JMMでの䞊べ替えの制限事項を正確に芋おみたしょう。 深く詳现な説明は、䟋えばThe JSR-133 Cookbookにありたすが、私はすべおを少し衚面的なレベルにし、おそらくいく぀かの制限を芋逃したす。 最も単玔で最も有名なロックから始めたしょう。



1.モニタヌの解攟は、同じモニタヌを取埗する 前に発生したす。 泚出口ではなくリリヌスです。぀たり、 wait



を䜿甚する堎合、セキュリティに぀いお心配する必芁はありたせん。



この知識が私たちの䟋を修正するのにどのように圹立぀か芋おみたしょう。 この堎合、すべおが非垞に簡単です。倖郚チェックを削陀し、同期をそのたたにしおおきたす。 これで、2番目のスレッドは、他のスレッドがモニタヌを解攟した埌にのみモニタヌを受け取るため、すべおの倉曎を確認するこずが保蚌されたす。 そしお、圌はすべおが初期化されるたで圌を手攟さないので、個別にではなく、すべおの倉曎を䞀床に芋るこずができたす



 public class Keeper { private Data data = null; public Data getData() { synchronized(this) { if(data == null) { data = new Data(); } } return data; } }
      
      





2. volatile倉数ぞの曞き蟌みは、同じ倉数から読み取る前に行われたす。



もちろん、私たちが行った倉曎は䞍正確さを修正したすが、元のコヌドを曞いた人を元のコヌド-毎回ロックに戻したす。 volatileキヌワヌドは保存できたす。 実際、怜蚎䞭のステヌトメント2は、volatileず宣蚀されたすべおを読み取るずきに、垞に珟圚の倀を取埗するこずを意味したす。 さらに、前述したように、揮発性フィヌルドの堎合、曞き蟌みは垞にlongおよびdoubleを含むアトミック操䜜です。 別の重芁な点他の゚ンティティたずえば、配列、リスト、たたは他のクラスぞの参照を持぀揮発性゚ンティティがある堎合、すべおではなく、゚ンティティ自䜓ぞの参照のみが垞に「新鮮」になりたす着信。



それでは、ダブルロックラムに戻りたしょう。 volatileを䜿甚するず、次のような状況を修正できたす。



 public class Keeper { private volatile Data data = null; public Data getData() { if(data == null) { synchronized(this) { if(data == null) { data = new Data(); } } } return data; } }
      
      





ここではただdata == null



堎合にのみロックがありdata == null



。 残りのケヌスでは、volatile読み取りを䜿甚しお陀倖したす。 揮発性ストアが揮発性読み取りの前に発生し、コンストラクタヌで発生するすべおの操䜜がフィヌルドの倀を読み取るナヌザヌに衚瀺されるずいう事実によっお、正確さが保蚌されたす。



さらに、ここでは興味深い仮定が䜿甚されおおり、確認する䟡倀がありたす。揮発性ストア+読み取りはロックよりも高速です。 ただし、同じパフォヌマンス゚ンゞニアが絶え間なく私たちに繰り返しおいるように、マむクロベンチマヌクは珟実ずはほずんど関係がありたせん。特に、枬定しようずしおいる仕組みがわからない堎合はそうです。 さらに、あなたがそれがどのように機胜するかを知っおいるず思うなら、あなたは間違いである可胜性が高く、重芁な芁玠を考慮に入れおいたせん。 ベンチマヌクを䜜成するための知識の深さには十分な自信がないので、ここではそのような枬定倀はありたせん。 ただし、このプレれンテヌションには 、スラむド54から始たる揮発性のパフォヌマンスに関する情報がありたすただし、すべおを読むこずを匷くお勧めしたす。 UPD蚭蚈䞊、volatileよりもvolatileよりもはるかに速いずいう興味深いコメントがありたす。



3. final



フィヌルドに倀を曞き蟌むおよび、このフィヌルドがリンクの堎合、このフィヌルドから到達可胜なすべおの倉数参照チェヌンを発生前オブゞェクトの䜜成時に、倖郚で発生する倉数にこのオブゞェクトを曞き蟌むこのコンストラクタ。



これもかなりわかりにくいですが、本質は単玔です final



フィヌルドを持぀オブゞェクトがある堎合、このオブゞェクトはこのfinal



フィヌルドおよびこのフィヌルドが参照できるすべおを蚭定した埌にのみ䜿甚できたす。 ただし、コンストラクタヌから倖郚の構築されたオブゞェクト぀たりthis



ぞのリンクを枡すず、誰かがあなたのオブゞェクトを未完成の状態で芋る可胜性があるこずを忘れないでください。



この䟋では、フィヌルド、最埌のレコヌド、 final



を䜜成するだけで十分であるこずがわかりたす。



 public class Data { String question; int answer; final int maxAllowedValue; public Data() { this.answer = 42; this.question = reverseEngineer(this.answer); this.maxAllowedValue = 9000; } private String reverseEngineer(int answer) { return null; } }
      
      





唯䞀のポむントは、魔法のように機胜するこずであり、あなたの独創的なレセプションを知らない人はあなたを理解できないかもしれたせん。 はい、あなたもこれをすぐに忘れるこずができたす。 もちろん、ここで䜕が起こっおいるのかを説明する「こっちのトリック」などの誇らしいコメントを远加するオプションもありたすが、䜕らかの理由でこれはあたり良い習慣ではないようです。



UPDこれは事実ではありたせん。 コメントは理由を説明したす。 UPD2 問題の議論に続いお、Ruslanは蚘事を曞きたした 。



さらに、フィヌルドも静的であり、JVMクラスは最初の呌び出しで1回だけ初期化を実行するこずが保蚌されおいるこずを芚えおおくこずが重芁です。 この堎合、同じシングルトン この蚘事のフレヌムワヌク内でパタヌンたたはアンチパタヌンずも呌ばれたせん。この蚘事ではたったく説明したせん。 を次のように実装できたす。



 public class Singleton { private Singleton() {} private static class InstanceContainer { private static final Singleton instance = new Singleton(); } public Singleton getInstance() { return InstanceContainer.instance; } }
      
      



もちろん、これはシングルトンを実装する方法に぀いおのアドバむスではありたせん。EffectiveJavaを読む人は誰でも、䜕らかの理由で突然䜕らかの理由でそれを曞くこずに決めた堎合、enumを䜿甚し、マルチスレッド、シリアル化、およびクロヌニングに関するすべおの問題の解決策を瀺しおいたす。 UPDシングルトンの最適な実装方法に぀いおは、 このトピックを参照しおください。



ずころで、Reflectionを䜿甚しお最終フィヌルドを倉曎できるこずを知っおいる人、およびそのような倉曎がどのように衚瀺されるかに興味がある人にずっお、私はこう蚀うこずができたす。「すべおがうたくいくように芋えたす。なぜか、すべおが本圓にあるかどうかは䞍明です。いいです。」 このトピックにはいく぀かのトピックがありたすが、これは最もむンタヌンです。 コメントで誰かがそれが実際にどのようになっおいるのかを教えおくれるなら、私は非垞に幞せです。 しかし、誰も蚀わない堎合、私自身が芋぀けお、確実に䌝えるでしょう。 UPD 圌らはコメントで蚀った。



確かに、この蚘事では取り䞊げなかった発生前の関係に関連する他の操䜜がいく぀かありたすが、それらは既により具䜓的であり、興味がある堎合は、 暙準たたは他の堎所で自分自身を芋぀けお共有するこずができたすコメントの党員。



クレゞット、リンク、およびもの

たず、前述のパフォヌマンス゚ンゞニアであるAlexei TheShade ShipilevずSergey Walrus Kuksenkoの臚床的なナンセンスの内容に関する蚘事のいく぀かの協議ず予備怜蚌に感謝したす。



たた、蚘事の執筆時に䜿甚した゜ヌスのリストず、トピックに関する適切なリンクも瀺したす。 コメントに興味深い質問、远加、修正を残しおください。読んで答えおうれしいです。



All Articles