Java: Javaメモリモデルの特性 と 原子性の保証について
プロローグ
こんにちは。私は九州生まれ九州在住ですが、こちらもここ最近急に寒くなって、夏服を着ることもなくなり服装をどうしたものかと悩んでます。パーカーが好きなので そろそろ新しいパーカーほしいなぁ~ なんて思っています。
さて前置きですが、この記事は、私が本やネットで勉強して自分なりに言葉にしたものです。自分の備忘録みたいなものです。 なので内容に関して誤りもあるかもしれませんが、あしからずご了承くださいませ。
テーマ
今回、JMMの特性 と 原子性の保証について 初心者なりにまとめたものを記載しました。
Java Memory Model の話
と本に書いてあった。
まず前提として、スレッドは、以下3パターンを共有しません。
- メソッドのローカル変数 の 値
- メソッドのパラメータ変数 の 値
- ThreadLocal変数 の 値
※対象が参照オブジェクトだと共有される可能性がある
※ThreadLocal型の変数は、各スレッドごとに自動的にコピーされ、スレッド間で共有されない
しかし、最初にあった「オブジェクトのフィールド値」について 1~3に含まれていないではないか
と思う私のような初心者は多いと思います。
Javaのプラットフォームスレッドや仮想スレッドは、OSのスレッド上で動いています。
(OSのスレッド <= プラットフォームスレッド <= 仮想スレッド)
各スレッドには CPU キャッシュやレジスタといった「高速なローカルメモリ領域」がある。

上の絵のスレッドAがあるフィールドを読んだとき、JVMやCPUは「何度もメインメモリにアクセスするのは遅い」と指示し、ローカルキャッシュに値を保持して再利用します。
これが
「JMMは、スレッドがオブジェクトを参照するとき、オブジェクトのフィールド値を、
各スレッドが保持する(キャッシュする)ことを許可している」
という言葉の意味となります。
こういったJavaメモリモデルの可視性問題を、回避する方法として、排他制御 や volatile修飾子の使用、アトミック処理があげられる。
※排他制御は別記事にあげるつもりです。
volatile修飾子
volatile修飾子をつけたフィールド宣言の例は以下となります。
private volatile int nCount;
private static volatile int nValue;
また、long型 や double型の変数の読み書きにおいて、アトミック性を保証させることができる
※アトミック性 とは 1回の操作が途中で分割されない(中断されない)こと
long,double以外のプリミティブ型はアトミック性を保証している。
じゃあ なぜ、このlong型 と double型だけ特別なのかは、
64bitになっていることに理由があるとのこと。
昔の Java(特に32bit JVM)では、64bit値の読み書きを「2回に分けて」処理することがあった。
上位32bitの書き込み
↓
下位32bitの書き込み
もし他のスレッドがその途中で読み込むと、半分古くて半分新しい値(いわゆる“壊れた値”)を読む
ことが起こりえるらしい。
最後に、重要なことで、volatile修飾子をつけても排他制御はできません。オブジェクト等の値を保証することで有効だと考えます。
アトミック処理
アトミック処理とは
つまりスレッドが複数あっても、他のスレッドがその処理の途中に割り込めない。
java.utill.concurrent.atomicパッケージを使用したアトミック処理の例として
import java.util.concurrent.atomic.AtomicInteger;
class MyItem {
private final AtomicInteger nCount = new AtomicInteger();
public void increment() {
this.nCount.getAndIncrement();
}
}
こうすることで、単純な nCount++ した場合の不正値を回避することができた。
エピローグ
記事にしていて、自分でもあれって思うところが多く誤りが多いかも。。。
今回のテーマでもし誤り、もしくはこうしたらいいのに と思うものがあればご指摘頂ければと思います。
以上です。
Discussion